@payload-onpage/react 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +13 -2
- package/dist/OnPageClient.d.ts +18 -0
- package/dist/OnPageClient.d.ts.map +1 -0
- package/dist/OnPageClient.js +51 -0
- package/dist/OnPageClient.js.map +1 -0
- package/dist/annotations.d.ts +55 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/annotations.js +60 -0
- package/dist/annotations.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +19 -0
- package/dist/context.js.map +1 -0
- package/dist/dom.d.ts +42 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +95 -0
- package/dist/dom.js.map +1 -0
- package/dist/editor/FieldHost.d.ts +5 -0
- package/dist/editor/FieldHost.d.ts.map +1 -0
- package/dist/editor/FieldHost.js +36 -0
- package/dist/editor/FieldHost.js.map +1 -0
- package/dist/editor/Ghosts.d.ts +6 -0
- package/dist/editor/Ghosts.d.ts.map +1 -0
- package/dist/editor/Ghosts.js +37 -0
- package/dist/editor/Ghosts.js.map +1 -0
- package/dist/editor/OnPageEditor.d.ts +25 -0
- package/dist/editor/OnPageEditor.d.ts.map +1 -0
- package/dist/editor/OnPageEditor.js +211 -0
- package/dist/editor/OnPageEditor.js.map +1 -0
- package/dist/editor/useGhosts.d.ts +24 -0
- package/dist/editor/useGhosts.d.ts.map +1 -0
- package/dist/editor/useGhosts.js +34 -0
- package/dist/editor/useGhosts.js.map +1 -0
- package/dist/editor/usePageEvents.d.ts +3 -0
- package/dist/editor/usePageEvents.d.ts.map +1 -0
- package/dist/editor/usePageEvents.js +164 -0
- package/dist/editor/usePageEvents.js.map +1 -0
- package/dist/editor-kit.d.ts +19 -0
- package/dist/editor-kit.d.ts.map +1 -0
- package/dist/editor-kit.js +17 -0
- package/dist/editor-kit.js.map +1 -0
- package/dist/editors/CheckboxEditor.d.ts +3 -0
- package/dist/editors/CheckboxEditor.d.ts.map +1 -0
- package/dist/editors/CheckboxEditor.js +10 -0
- package/dist/editors/CheckboxEditor.js.map +1 -0
- package/dist/editors/ColorEditor.d.ts +4 -0
- package/dist/editors/ColorEditor.d.ts.map +1 -0
- package/dist/editors/ColorEditor.js +21 -0
- package/dist/editors/ColorEditor.js.map +1 -0
- package/dist/editors/DateEditor.d.ts +3 -0
- package/dist/editors/DateEditor.d.ts.map +1 -0
- package/dist/editors/DateEditor.js +28 -0
- package/dist/editors/DateEditor.js.map +1 -0
- package/dist/editors/InlineText.d.ts +4 -0
- package/dist/editors/InlineText.d.ts.map +1 -0
- package/dist/editors/InlineText.js +77 -0
- package/dist/editors/InlineText.js.map +1 -0
- package/dist/editors/NumberEditor.d.ts +4 -0
- package/dist/editors/NumberEditor.d.ts.map +1 -0
- package/dist/editors/NumberEditor.js +23 -0
- package/dist/editors/NumberEditor.js.map +1 -0
- package/dist/editors/RelationshipEditor.d.ts +3 -0
- package/dist/editors/RelationshipEditor.d.ts.map +1 -0
- package/dist/editors/RelationshipEditor.js +60 -0
- package/dist/editors/RelationshipEditor.js.map +1 -0
- package/dist/editors/SelectEditor.d.ts +4 -0
- package/dist/editors/SelectEditor.d.ts.map +1 -0
- package/dist/editors/SelectEditor.js +36 -0
- package/dist/editors/SelectEditor.js.map +1 -0
- package/dist/editors/UploadEditor.d.ts +3 -0
- package/dist/editors/UploadEditor.d.ts.map +1 -0
- package/dist/editors/UploadEditor.js +48 -0
- package/dist/editors/UploadEditor.js.map +1 -0
- package/dist/editors/index.d.ts +13 -0
- package/dist/editors/index.d.ts.map +1 -0
- package/dist/editors/index.js +23 -0
- package/dist/editors/index.js.map +1 -0
- package/dist/editors/useDebouncedSearch.d.ts +17 -0
- package/dist/editors/useDebouncedSearch.d.ts.map +1 -0
- package/dist/editors/useDebouncedSearch.js +50 -0
- package/dist/editors/useDebouncedSearch.js.map +1 -0
- package/dist/hub.d.ts +14 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +20 -0
- package/dist/hub.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +12 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +83 -0
- package/dist/layout.js.map +1 -0
- package/dist/locators.d.ts +19 -0
- package/dist/locators.d.ts.map +1 -0
- package/dist/locators.js +215 -0
- package/dist/locators.js.map +1 -0
- package/dist/messages.d.ts +100 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +112 -0
- package/dist/messages.js.map +1 -0
- package/dist/registry.d.ts +15 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +29 -0
- package/dist/registry.js.map +1 -0
- package/dist/renderers.d.ts +7 -0
- package/dist/renderers.d.ts.map +1 -0
- package/dist/renderers.js +12 -0
- package/dist/renderers.js.map +1 -0
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +49 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/styles.d.ts +8 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +122 -0
- package/dist/styles.js.map +1 -0
- package/dist/targets.d.ts +18 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +86 -0
- package/dist/targets.js.map +1 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/BlockPicker.d.ts +3 -0
- package/dist/ui/BlockPicker.d.ts.map +1 -0
- package/dist/ui/BlockPicker.js +27 -0
- package/dist/ui/BlockPicker.js.map +1 -0
- package/dist/ui/Dialog.d.ts +22 -0
- package/dist/ui/Dialog.d.ts.map +1 -0
- package/dist/ui/Dialog.js +83 -0
- package/dist/ui/Dialog.js.map +1 -0
- package/dist/ui/Dock.d.ts +7 -0
- package/dist/ui/Dock.d.ts.map +1 -0
- package/dist/ui/Dock.js +104 -0
- package/dist/ui/Dock.js.map +1 -0
- package/dist/ui/Overlay.d.ts +2 -0
- package/dist/ui/Overlay.d.ts.map +1 -0
- package/dist/ui/Overlay.js +150 -0
- package/dist/ui/Overlay.js.map +1 -0
- package/dist/ui/PostButton.d.ts +10 -0
- package/dist/ui/PostButton.d.ts.map +1 -0
- package/dist/ui/PostButton.js +7 -0
- package/dist/ui/PostButton.js.map +1 -0
- package/dist/ui/PreviewToolbar.d.ts +15 -0
- package/dist/ui/PreviewToolbar.d.ts.map +1 -0
- package/dist/ui/PreviewToolbar.js +45 -0
- package/dist/ui/PreviewToolbar.js.map +1 -0
- package/dist/ui/SettingsPanel.d.ts +11 -0
- package/dist/ui/SettingsPanel.d.ts.map +1 -0
- package/dist/ui/SettingsPanel.js +130 -0
- package/dist/ui/SettingsPanel.js.map +1 -0
- package/dist/useEvent.d.ts +7 -0
- package/dist/useEvent.d.ts.map +1 -0
- package/dist/useEvent.js +15 -0
- package/dist/useEvent.js.map +1 -0
- package/package.json +71 -4
- package/src/OnPageClient.tsx +67 -0
- package/src/annotations.ts +83 -0
- package/src/context.tsx +23 -0
- package/src/dom.ts +118 -0
- package/src/editor/FieldHost.tsx +66 -0
- package/src/editor/Ghosts.tsx +55 -0
- package/src/editor/OnPageEditor.tsx +315 -0
- package/src/editor/useGhosts.ts +64 -0
- package/src/editor/usePageEvents.ts +161 -0
- package/src/editor-kit.ts +27 -0
- package/src/editors/CheckboxEditor.tsx +31 -0
- package/src/editors/ColorEditor.tsx +64 -0
- package/src/editors/DateEditor.tsx +64 -0
- package/src/editors/InlineText.tsx +106 -0
- package/src/editors/NumberEditor.tsx +69 -0
- package/src/editors/RelationshipEditor.tsx +153 -0
- package/src/editors/SelectEditor.tsx +74 -0
- package/src/editors/UploadEditor.tsx +130 -0
- package/src/editors/index.ts +35 -0
- package/src/editors/useDebouncedSearch.ts +54 -0
- package/src/hub.ts +32 -0
- package/src/index.ts +55 -0
- package/src/layout.ts +83 -0
- package/src/locators.ts +210 -0
- package/src/messages.ts +120 -0
- package/src/registry.ts +43 -0
- package/src/renderers.ts +15 -0
- package/src/sanitize.ts +52 -0
- package/src/styles.ts +122 -0
- package/src/targets.ts +101 -0
- package/src/types.ts +132 -0
- package/src/ui/BlockPicker.tsx +49 -0
- package/src/ui/Dialog.tsx +159 -0
- package/src/ui/Dock.tsx +240 -0
- package/src/ui/Overlay.tsx +324 -0
- package/src/ui/PostButton.tsx +26 -0
- package/src/ui/PreviewToolbar.tsx +123 -0
- package/src/ui/SettingsPanel.tsx +279 -0
- package/src/useEvent.ts +16 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Settings for one section or item, generated from the schema: every value
|
|
5
|
+
* that isn't edited directly on the page — selects (layout, background, width,
|
|
6
|
+
* spacing…), checkboxes, numbers, dates, relationships, URLs and other plain
|
|
7
|
+
* text, images that aren't on the page. Changes apply immediately.
|
|
8
|
+
*/
|
|
9
|
+
import { fieldAtPath, getAt, relationTitle, toText } from '@payload-onpage/core';
|
|
10
|
+
import { useId, useRef, useState } from 'react';
|
|
11
|
+
import { useOnPage } from "../context.js";
|
|
12
|
+
import { Dialog } from "./Dialog.js";
|
|
13
|
+
const SKIP = new Set(['id', 'blockType', 'blockName']);
|
|
14
|
+
const TEXTUAL = new Set(['text', 'textarea', 'email', 'number', 'date', 'relationship', 'upload']);
|
|
15
|
+
const INPUT_TYPES = new Set(['text', 'textarea', 'email', 'number']);
|
|
16
|
+
function settable(field, path, key, targets) {
|
|
17
|
+
if (SKIP.has(field.name))
|
|
18
|
+
return false;
|
|
19
|
+
const full = `${path}.${field.name}`;
|
|
20
|
+
switch (field.type) {
|
|
21
|
+
case 'select':
|
|
22
|
+
case 'radio':
|
|
23
|
+
case 'checkbox':
|
|
24
|
+
return true;
|
|
25
|
+
case 'group':
|
|
26
|
+
return (field.fields ?? []).some((f) => settable(f, full, key, targets));
|
|
27
|
+
default:
|
|
28
|
+
// Shown on the page? Edit it there.
|
|
29
|
+
return TEXTUAL.has(field.type) && !targets.find(key, full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const hasSettings = (fields, path, key, targets) => fields.some((f) => settable(f, path, key, targets));
|
|
33
|
+
const display = (v, t) => {
|
|
34
|
+
if (v == null || v === '')
|
|
35
|
+
return t('common.none');
|
|
36
|
+
if (Array.isArray(v))
|
|
37
|
+
return v.length ? t('common.selected', { count: v.length }) : t('common.none');
|
|
38
|
+
return typeof v === 'object' ? relationTitle(v) : toText(v);
|
|
39
|
+
};
|
|
40
|
+
function TextInput({ docKey, path, field, id, }) {
|
|
41
|
+
const { session, snap } = useOnPage();
|
|
42
|
+
const value = getAt(snap.docs[docKey]?.doc, path);
|
|
43
|
+
const [draft, setDraft] = useState(null);
|
|
44
|
+
const shown = draft ?? toText(value);
|
|
45
|
+
const parse = (v) => (field.type === 'number' ? (v === '' ? null : Number(v)) : v);
|
|
46
|
+
const commit = () => {
|
|
47
|
+
if (draft === null)
|
|
48
|
+
return;
|
|
49
|
+
session.setValue(docKey, path, parse(draft));
|
|
50
|
+
setDraft(null);
|
|
51
|
+
};
|
|
52
|
+
return (_jsx("input", { id: id, className: "po-input", type: field.type === 'number' ? 'number' : field.type === 'email' ? 'email' : 'text', value: shown, onChange: (e) => {
|
|
53
|
+
setDraft(e.target.value);
|
|
54
|
+
session.setValue(docKey, path, parse(e.target.value), { transient: true });
|
|
55
|
+
}, onBlur: commit, onKeyDown: (e) => e.key === 'Enter' && e.target.blur(), "data-testid": `onpage-setting-${path}` }));
|
|
56
|
+
}
|
|
57
|
+
/** A radio group with arrow-key navigation (WAI-ARIA radio pattern). */
|
|
58
|
+
function Segmented({ docKey, path, field, labelId, }) {
|
|
59
|
+
const { session, snap } = useOnPage();
|
|
60
|
+
const options = field.options ?? [];
|
|
61
|
+
const current = getAt(snap.docs[docKey]?.doc, path) ?? field.defaultValue ?? '';
|
|
62
|
+
const index = Math.max(0, options.findIndex((o) => o.value === current));
|
|
63
|
+
const onKey = (e) => {
|
|
64
|
+
const step = e.key === 'ArrowRight' || e.key === 'ArrowDown'
|
|
65
|
+
? 1
|
|
66
|
+
: e.key === 'ArrowLeft' || e.key === 'ArrowUp'
|
|
67
|
+
? -1
|
|
68
|
+
: 0;
|
|
69
|
+
if (!step)
|
|
70
|
+
return;
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
const next = options[(index + step + options.length) % options.length];
|
|
73
|
+
if (next)
|
|
74
|
+
session.setValue(docKey, path, next.value);
|
|
75
|
+
const group = e.currentTarget.parentElement;
|
|
76
|
+
requestAnimationFrame(() => group?.querySelector('[aria-checked="true"]')?.focus());
|
|
77
|
+
};
|
|
78
|
+
return (_jsx("div", { className: "po-seg", role: "radiogroup", "aria-labelledby": labelId, children: options.map((o, i) => (_jsx("button", { type: "button", role: "radio", "aria-checked": current === o.value, tabIndex: i === index ? 0 : -1, onClick: () => session.setValue(docKey, path, o.value), onKeyDown: onKey, "data-testid": `onpage-setting-${path}-${o.value}`, children: o.label }, o.value))) }));
|
|
79
|
+
}
|
|
80
|
+
function Control({ docKey, path, field, anchor, id, labelId, }) {
|
|
81
|
+
const { session, snap, edit, t } = useOnPage();
|
|
82
|
+
const value = getAt(snap.docs[docKey]?.doc, path);
|
|
83
|
+
const set = (v) => session.setValue(docKey, path, v);
|
|
84
|
+
if ((field.type === 'select' || field.type === 'radio') && !field.hasMany) {
|
|
85
|
+
const options = field.options ?? [];
|
|
86
|
+
if (options.length <= 5)
|
|
87
|
+
return _jsx(Segmented, { docKey: docKey, path: path, field: field, labelId: labelId });
|
|
88
|
+
return (_jsxs("select", { id: id, className: "po-select", value: toText(value ?? field.defaultValue), onChange: (e) => set(e.target.value || null), "data-testid": `onpage-setting-${path}`, children: [!field.required && _jsx("option", { value: "", children: t('common.none') }), options.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value)))] }));
|
|
89
|
+
}
|
|
90
|
+
if (field.type === 'checkbox') {
|
|
91
|
+
return (_jsxs("label", { className: "po-check", children: [_jsx("input", { type: "checkbox", checked: Boolean(value ?? field.defaultValue), onChange: (e) => set(e.target.checked), "data-testid": `onpage-setting-${path}` }), field.label] }));
|
|
92
|
+
}
|
|
93
|
+
if (INPUT_TYPES.has(field.type))
|
|
94
|
+
return _jsx(TextInput, { docKey: docKey, path: path, field: field, id: id });
|
|
95
|
+
// Everything else opens its field editor, anchored to the panel row.
|
|
96
|
+
return (_jsx("button", { id: id, type: "button", className: "po-input po-picker", onClick: () => edit(docKey, path, { anchor: anchor.current ?? undefined }), "data-testid": `onpage-setting-${path}`, children: field.type === 'upload'
|
|
97
|
+
? t(value ? 'settings.changeImage' : 'settings.chooseImage')
|
|
98
|
+
: display(value, t) }));
|
|
99
|
+
}
|
|
100
|
+
function Row({ docKey, path, field }) {
|
|
101
|
+
const anchor = useRef(null);
|
|
102
|
+
const id = useId();
|
|
103
|
+
const labelId = `${id}-label`;
|
|
104
|
+
return (_jsxs("div", { className: "po-row", ref: anchor, children: [field.type !== 'checkbox' && (_jsx("label", { id: labelId, htmlFor: id, children: field.label })), _jsx(Control, { docKey: docKey, path: path, field: field, anchor: anchor, id: id, labelId: labelId }), field.description && _jsx("small", { children: field.description })] }));
|
|
105
|
+
}
|
|
106
|
+
function Fields({ docKey, fields, path }) {
|
|
107
|
+
const { targets } = useOnPage();
|
|
108
|
+
return (_jsx(_Fragment, { children: fields
|
|
109
|
+
.filter((f) => settable(f, path, docKey, targets))
|
|
110
|
+
.map((f) => {
|
|
111
|
+
const full = `${path}.${f.name}`;
|
|
112
|
+
if (f.type === 'group') {
|
|
113
|
+
return (_jsxs("fieldset", { className: "po-group", children: [_jsx("legend", { className: "po-h4", children: f.label }), _jsx(Fields, { docKey: docKey, fields: f.fields ?? [], path: full })] }, f.name));
|
|
114
|
+
}
|
|
115
|
+
return _jsx(Row, { docKey: docKey, path: full, field: f }, f.name);
|
|
116
|
+
}) }));
|
|
117
|
+
}
|
|
118
|
+
export function SettingsPanel() {
|
|
119
|
+
const { panel, setPanel, snap, t } = useOnPage();
|
|
120
|
+
if (panel?.kind !== 'settings')
|
|
121
|
+
return null;
|
|
122
|
+
const state = snap.docs[panel.key];
|
|
123
|
+
if (!state?.schema || !state.doc)
|
|
124
|
+
return null;
|
|
125
|
+
const owner = fieldAtPath(state.schema.fields, state.doc, panel.path);
|
|
126
|
+
// Row fields: the block's fields for a blocks row, the item fields for an array row.
|
|
127
|
+
const fields = owner?.field.type === 'blocks' ? (owner.block?.fields ?? []) : (owner?.field.fields ?? []);
|
|
128
|
+
return (_jsx(Dialog, { variant: "panel", title: t('settings.title', { label: panel.title }), onClose: () => setPanel(null), t: t, testId: "onpage-settings", children: _jsx(Fields, { docKey: panel.key, fields: fields, path: panel.path }) }));
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=SettingsPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsPanel.js","sourceRoot":"","sources":["../../src/ui/SettingsPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable function that always calls the latest `fn` (the `useEffectEvent`
|
|
3
|
+
* pattern). Our own, because the React bundled with Next.js 15 doesn't export
|
|
4
|
+
* `useEffectEvent`. Call it from effects and event handlers, not during render.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useEvent<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
|
|
7
|
+
//# sourceMappingURL=useEvent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEvent.d.ts","sourceRoot":"","sources":["../src/useEvent.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAMzF"}
|
package/dist/useEvent.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useInsertionEffect, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* A stable function that always calls the latest `fn` (the `useEffectEvent`
|
|
5
|
+
* pattern). Our own, because the React bundled with Next.js 15 doesn't export
|
|
6
|
+
* `useEffectEvent`. Call it from effects and event handlers, not during render.
|
|
7
|
+
*/
|
|
8
|
+
export function useEvent(fn) {
|
|
9
|
+
const latest = useRef(fn);
|
|
10
|
+
useInsertionEffect(() => {
|
|
11
|
+
latest.current = fn;
|
|
12
|
+
});
|
|
13
|
+
return useCallback((...args) => latest.current(...args), []);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=useEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEvent.js","sourceRoot":"","sources":["../src/useEvent.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE/D;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAyB,EAAqB;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAA;IACzB,kBAAkB,CAAC,GAAG,EAAE;QACtB,MAAM,CAAC,OAAO,GAAG,EAAE,CAAA;IACrB,CAAC,CAAC,CAAA;IACF,OAAO,WAAW,CAAC,CAAC,GAAG,IAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;AACjE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payload-onpage/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "payload-onpage React editor: overlay, toolbars, field editors, annotations and client mode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/jellologic/payload-onpage.git",
|
|
11
|
+
"directory": "packages/react"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/jellologic/payload-onpage#readme",
|
|
14
|
+
"bugs": "https://github.com/jellologic/payload-onpage/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"payload",
|
|
17
|
+
"payloadcms",
|
|
18
|
+
"payload-plugin",
|
|
19
|
+
"nextjs",
|
|
20
|
+
"visual-editing",
|
|
21
|
+
"live-preview",
|
|
22
|
+
"inline-editing",
|
|
23
|
+
"cms"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./annotations": {
|
|
31
|
+
"types": "./dist/annotations.d.ts",
|
|
32
|
+
"default": "./dist/annotations.js"
|
|
33
|
+
},
|
|
34
|
+
"./client": {
|
|
35
|
+
"types": "./dist/OnPageClient.d.ts",
|
|
36
|
+
"default": "./dist/OnPageClient.js"
|
|
37
|
+
},
|
|
38
|
+
"./editor-kit": {
|
|
39
|
+
"types": "./dist/editor-kit.d.ts",
|
|
40
|
+
"default": "./dist/editor-kit.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"main": "./dist/index.js",
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"!dist/.tsbuildinfo",
|
|
48
|
+
"src",
|
|
49
|
+
"!src/**/__tests__",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -b",
|
|
55
|
+
"typecheck": "tsc -b",
|
|
56
|
+
"test": "bun test src",
|
|
57
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public",
|
|
61
|
+
"provenance": true
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@payload-onpage/core": "0.1.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"react": "^19.0.0",
|
|
68
|
+
"react-dom": "^19.0.0"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=20.9.0"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client mode: render a document from editor state, so edits show instantly
|
|
5
|
+
* (no save → server render round trip). Outside an editor it just renders the
|
|
6
|
+
* document it was given.
|
|
7
|
+
*
|
|
8
|
+
* <OnPageClient doc={page} collection="pages" render={(page, e) => <Page page={page} e={e} />} />
|
|
9
|
+
*
|
|
10
|
+
* `e` is the document's annotation helper (`e.field('title')`, `e.row('layout.0')`).
|
|
11
|
+
* Text is auto-mapped as in refresh mode: the rendered state carries the same
|
|
12
|
+
* invisible markers (turn off with `autoMap={false}`).
|
|
13
|
+
*/
|
|
14
|
+
import { useEvent } from './useEvent.ts'
|
|
15
|
+
import { encodeDocument, refKey, type DocRef } from '@payload-onpage/core'
|
|
16
|
+
import { useEffect, useMemo, type ReactNode } from 'react'
|
|
17
|
+
import { editable, editableOff, type Editable } from './annotations.ts'
|
|
18
|
+
import { useOnPageOptional } from './context.tsx'
|
|
19
|
+
|
|
20
|
+
export type OnPageClientProps<T> = {
|
|
21
|
+
doc: T
|
|
22
|
+
render: (doc: T, e: Editable) => ReactNode
|
|
23
|
+
/** Mark rendered text so it maps to its fields without annotations. Default true. */
|
|
24
|
+
autoMap?: boolean
|
|
25
|
+
} & ({ collection: string; global?: never } | { global: string; collection?: never })
|
|
26
|
+
|
|
27
|
+
export function OnPageClient<T extends { id?: string | number } | null | undefined>(
|
|
28
|
+
props: OnPageClientProps<T>,
|
|
29
|
+
) {
|
|
30
|
+
const { doc, render, global: globalSlug, collection } = props
|
|
31
|
+
const ctx = useOnPageOptional()
|
|
32
|
+
const id = doc?.id
|
|
33
|
+
const ref = useMemo<DocRef | null>(
|
|
34
|
+
() =>
|
|
35
|
+
globalSlug
|
|
36
|
+
? { kind: 'global', slug: globalSlug }
|
|
37
|
+
: collection && id != null
|
|
38
|
+
? { kind: 'collection', slug: collection, id }
|
|
39
|
+
: null,
|
|
40
|
+
[globalSlug, collection, id],
|
|
41
|
+
)
|
|
42
|
+
const key = ref ? refKey(ref) : null
|
|
43
|
+
const enabled = !!ctx && !!ref
|
|
44
|
+
|
|
45
|
+
// Join the editor once it's there: render from its state (it adopts this page's value).
|
|
46
|
+
const join = useEvent((r: DocRef) => {
|
|
47
|
+
if (!ctx) return undefined
|
|
48
|
+
const off = ctx.registerClientDoc(refKey(r))
|
|
49
|
+
void ctx.session.open(r, doc ?? undefined)
|
|
50
|
+
return off
|
|
51
|
+
})
|
|
52
|
+
useEffect(() => (enabled && ref ? join(ref) : undefined), [enabled, ref, join])
|
|
53
|
+
|
|
54
|
+
const state = key ? ctx?.snap.docs[key] : undefined
|
|
55
|
+
const stateDoc = state?.doc
|
|
56
|
+
const fields = state?.schema?.fields
|
|
57
|
+
const autoMap = props.autoMap !== false
|
|
58
|
+
const current = useMemo(() => {
|
|
59
|
+
if (!stateDoc) return doc
|
|
60
|
+
return autoMap && fields && key ? encodeDocument(stateDoc, fields, key) : stateDoc
|
|
61
|
+
}, [stateDoc, fields, key, autoMap, doc])
|
|
62
|
+
const e = useMemo(
|
|
63
|
+
() => (enabled && ref ? editable(ref, { enabled: true }) : editableOff),
|
|
64
|
+
[enabled, ref],
|
|
65
|
+
)
|
|
66
|
+
return <>{render(current as T, e)}</>
|
|
67
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotations — optional, precise mapping from elements to fields. Pure and
|
|
3
|
+
* dependency-free, so they work in server components. When `enabled` is false
|
|
4
|
+
* every helper returns `{}`: public HTML carries nothing.
|
|
5
|
+
*
|
|
6
|
+
* const e = editable({ collection: 'pages', doc: page }, { enabled })
|
|
7
|
+
* <section {...e.row('layout.2')}>
|
|
8
|
+
* <h2 {...e.field('layout.2.heading')}>…</h2>
|
|
9
|
+
* <img {...e.media('layout.2.image')} />
|
|
10
|
+
* <ul {...e.list('layout.2.items', 'Item')}> <li {...e.row('layout.2.items.0')}>
|
|
11
|
+
*
|
|
12
|
+
* Auto-mapping (stega) already maps most text without any of this; annotate
|
|
13
|
+
* images that have no alt, layout rows you want exact, and lists ("+ Add").
|
|
14
|
+
*/
|
|
15
|
+
import { refKey, type DocRef, type TargetKind } from '@payload-onpage/core'
|
|
16
|
+
|
|
17
|
+
/** What an annotated element shows (`field` = resolve from the schema). */
|
|
18
|
+
export type FieldKind = Exclude<TargetKind, 'item' | 'list'>
|
|
19
|
+
|
|
20
|
+
export const ATTR = {
|
|
21
|
+
ref: 'data-onpage-ref',
|
|
22
|
+
field: 'data-onpage-field',
|
|
23
|
+
kind: 'data-onpage-kind',
|
|
24
|
+
row: 'data-onpage-row',
|
|
25
|
+
list: 'data-onpage-list',
|
|
26
|
+
label: 'data-onpage-label',
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
type Attrs = Record<string, string>
|
|
30
|
+
|
|
31
|
+
export interface Editable {
|
|
32
|
+
enabled: boolean
|
|
33
|
+
/** An element showing one field. `kind` defaults to "field" (resolved from the schema). */
|
|
34
|
+
field(path: string, kind?: FieldKind, label?: string): Attrs
|
|
35
|
+
/** An element showing an upload field (image). */
|
|
36
|
+
media(path: string, label?: string): Attrs
|
|
37
|
+
/** A row of a blocks or array field (a section, a card, a button). */
|
|
38
|
+
row(path: string, label?: string): Attrs
|
|
39
|
+
/** The element holding an array's rows (gets "+ Add"). `itemLabel` names one row. */
|
|
40
|
+
list(path: string, itemLabel?: string): Attrs
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const none = (): Attrs => ({})
|
|
44
|
+
export const editableOff: Editable = {
|
|
45
|
+
enabled: false,
|
|
46
|
+
field: none,
|
|
47
|
+
media: none,
|
|
48
|
+
row: none,
|
|
49
|
+
list: none,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type EditableTarget =
|
|
53
|
+
| DocRef
|
|
54
|
+
| { collection: string; doc: { id?: string | number } | null | undefined }
|
|
55
|
+
| { global: string }
|
|
56
|
+
|
|
57
|
+
export function toRef(t: EditableTarget): DocRef | null {
|
|
58
|
+
if ('kind' in t) return t
|
|
59
|
+
if ('global' in t) return { kind: 'global', slug: t.global }
|
|
60
|
+
return t.doc?.id == null ? null : { kind: 'collection', slug: t.collection, id: t.doc.id }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Annotation helpers bound to one document. */
|
|
64
|
+
export function editable(target: EditableTarget, opts: { enabled: boolean }): Editable {
|
|
65
|
+
const ref = opts.enabled ? toRef(target) : null
|
|
66
|
+
if (!ref) return editableOff
|
|
67
|
+
const key = refKey(ref)
|
|
68
|
+
const base = (extra: Attrs, label?: string): Attrs => ({
|
|
69
|
+
[ATTR.ref]: key,
|
|
70
|
+
...extra,
|
|
71
|
+
...(label ? { [ATTR.label]: label } : {}),
|
|
72
|
+
})
|
|
73
|
+
return {
|
|
74
|
+
enabled: true,
|
|
75
|
+
field: (path, kind = 'field', label) => base({ [ATTR.field]: path, [ATTR.kind]: kind }, label),
|
|
76
|
+
media: (path, label) => base({ [ATTR.field]: path, [ATTR.kind]: 'media' }, label),
|
|
77
|
+
row: (path, label) => base({ [ATTR.row]: path }, label),
|
|
78
|
+
list: (path, itemLabel) => base({ [ATTR.list]: path }, itemLabel),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Join path parts: p("layout", 2, "heading") → "layout.2.heading". */
|
|
83
|
+
export const p = (...parts: Array<string | number>): string => parts.join('.')
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useSyncExternalStore } from 'react'
|
|
4
|
+
import { hub } from './hub.ts'
|
|
5
|
+
import type { OnPageContextValue } from './types.ts'
|
|
6
|
+
|
|
7
|
+
export const OnPageContext = createContext<OnPageContextValue | null>(null)
|
|
8
|
+
|
|
9
|
+
/** The editor, for code rendered inside it (field editors, panels). */
|
|
10
|
+
export function useOnPage(): OnPageContextValue {
|
|
11
|
+
const ctx = useContext(OnPageContext)
|
|
12
|
+
if (!ctx) throw new Error('useOnPage must be used inside <OnPageEditor>')
|
|
13
|
+
return ctx
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const getNull = () => null
|
|
17
|
+
|
|
18
|
+
/** The editor, from inside it or anywhere on the page; null when no editor is mounted (public pages). */
|
|
19
|
+
export function useOnPageOptional(): OnPageContextValue | null {
|
|
20
|
+
const inside = useContext(OnPageContext)
|
|
21
|
+
const outside = useSyncExternalStore(hub.subscribe, hub.get, getNull)
|
|
22
|
+
return inside ?? outside
|
|
23
|
+
}
|
package/src/dom.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** DOM helpers shared by the editor UI (browser-only, no React). */
|
|
2
|
+
|
|
3
|
+
/** Attribute that hides a page element while an editor or ghost stands in for it. */
|
|
4
|
+
export const HIDDEN = 'data-onpage-hidden'
|
|
5
|
+
/** Marks the editor's own UI, which locators and click handling ignore. */
|
|
6
|
+
export const UI = 'data-onpage-ui'
|
|
7
|
+
|
|
8
|
+
/** Computed styles copied onto an inline editor so it looks like the element it replaces. */
|
|
9
|
+
export const TYPOGRAPHY = [
|
|
10
|
+
'fontFamily',
|
|
11
|
+
'fontSize',
|
|
12
|
+
'fontWeight',
|
|
13
|
+
'fontStyle',
|
|
14
|
+
'lineHeight',
|
|
15
|
+
'letterSpacing',
|
|
16
|
+
'wordSpacing',
|
|
17
|
+
'textTransform',
|
|
18
|
+
'textAlign',
|
|
19
|
+
'textIndent',
|
|
20
|
+
'color',
|
|
21
|
+
'paddingTop',
|
|
22
|
+
'paddingRight',
|
|
23
|
+
'paddingBottom',
|
|
24
|
+
'paddingLeft',
|
|
25
|
+
] as const
|
|
26
|
+
|
|
27
|
+
export function copyTypography(from: Element, to: HTMLElement): void {
|
|
28
|
+
const cs = getComputedStyle(from)
|
|
29
|
+
for (const k of TYPOGRAPHY) to.style[k] = cs[k]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The typography inline styles of an element (for a ghost that must look like it). */
|
|
33
|
+
export function typographyOf(el: HTMLElement): Partial<CSSStyleDeclaration> {
|
|
34
|
+
const out: Partial<CSSStyleDeclaration> = {}
|
|
35
|
+
for (const k of TYPOGRAPHY) out[k] = el.style[k]
|
|
36
|
+
return out
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Place an absolutely positioned (document layer) box over `el`. */
|
|
40
|
+
export function placeOver(
|
|
41
|
+
box: HTMLElement,
|
|
42
|
+
el: Element,
|
|
43
|
+
opts: { minWidth?: number; minHeight?: boolean } = {},
|
|
44
|
+
): void {
|
|
45
|
+
const r = el.getBoundingClientRect()
|
|
46
|
+
box.style.top = `${r.top + window.scrollY}px`
|
|
47
|
+
box.style.left = `${r.left + window.scrollX}px`
|
|
48
|
+
box.style.width = `${Math.max(r.width, opts.minWidth ?? 0)}px`
|
|
49
|
+
if (opts.minHeight) box.style.minHeight = `${r.height}px`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Inside the editor's own UI? */
|
|
53
|
+
export const isOurs = (node: EventTarget | null): boolean =>
|
|
54
|
+
node instanceof Element && !!node.closest(`[${UI}]`)
|
|
55
|
+
|
|
56
|
+
/** Focus is in a text input or editable region (keyboard shortcuts stay out of the way). */
|
|
57
|
+
export const isTyping = (t: EventTarget | null): boolean =>
|
|
58
|
+
t instanceof HTMLElement &&
|
|
59
|
+
(t.isContentEditable ||
|
|
60
|
+
t.tagName === 'INPUT' ||
|
|
61
|
+
t.tagName === 'TEXTAREA' ||
|
|
62
|
+
t.tagName === 'SELECT')
|
|
63
|
+
|
|
64
|
+
/** The DOM caret position under a viewport point, if it falls inside `within`. */
|
|
65
|
+
export function caretAt(
|
|
66
|
+
point: { x: number; y: number },
|
|
67
|
+
within: Element,
|
|
68
|
+
): { node: Node; offset: number } | null {
|
|
69
|
+
const doc = document as Document & {
|
|
70
|
+
caretPositionFromPoint?: (x: number, y: number) => { offsetNode: Node; offset: number } | null
|
|
71
|
+
caretRangeFromPoint?: (x: number, y: number) => Range | null
|
|
72
|
+
}
|
|
73
|
+
const pos = doc.caretPositionFromPoint?.(point.x, point.y)
|
|
74
|
+
if (pos && within.contains(pos.offsetNode)) return { node: pos.offsetNode, offset: pos.offset }
|
|
75
|
+
const r = doc.caretRangeFromPoint?.(point.x, point.y)
|
|
76
|
+
if (r && within.contains(r.startContainer))
|
|
77
|
+
return { node: r.startContainer, offset: r.startOffset }
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Place the caret at a viewport point inside `el`, else at the end. */
|
|
82
|
+
export function placeCaret(el: HTMLElement, point?: { x: number; y: number }): void {
|
|
83
|
+
const sel = window.getSelection()
|
|
84
|
+
if (!sel) return
|
|
85
|
+
const range = document.createRange()
|
|
86
|
+
const hit = point ? caretAt(point, el) : null
|
|
87
|
+
if (hit) range.setStart(hit.node, hit.offset)
|
|
88
|
+
else {
|
|
89
|
+
range.selectNodeContents(el)
|
|
90
|
+
range.collapse(false)
|
|
91
|
+
}
|
|
92
|
+
range.collapse(true)
|
|
93
|
+
sel.removeAllRanges()
|
|
94
|
+
sel.addRange(range)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Position a fixed popover below (or above) an element, kept inside the viewport. */
|
|
98
|
+
export function popoverPosition(
|
|
99
|
+
el: Element | null,
|
|
100
|
+
size = { width: 300, height: 320 },
|
|
101
|
+
): { top: number; left: number } {
|
|
102
|
+
const vw = window.innerWidth
|
|
103
|
+
const vh = window.innerHeight
|
|
104
|
+
if (!el) return { top: 80, left: Math.max(8, vw / 2 - size.width / 2) }
|
|
105
|
+
const r = el.getBoundingClientRect()
|
|
106
|
+
const below = r.bottom + 8
|
|
107
|
+
const top = below + size.height < vh ? below : Math.max(8, r.top - 8 - size.height)
|
|
108
|
+
const left = Math.min(Math.max(8, r.left), vw - size.width - 8)
|
|
109
|
+
return { top, left }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const FOCUSABLE =
|
|
113
|
+
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
114
|
+
|
|
115
|
+
export const focusableIn = (root: Element): HTMLElement[] =>
|
|
116
|
+
Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(
|
|
117
|
+
(el) => !el.hasAttribute('disabled'),
|
|
118
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { fieldAtPath, getAt } from '@payload-onpage/core'
|
|
4
|
+
import { Suspense, useCallback } from 'react'
|
|
5
|
+
import { useOnPage } from '../context.tsx'
|
|
6
|
+
import type { FieldEditorProps } from '../types.ts'
|
|
7
|
+
|
|
8
|
+
/** Renders the open field editor, if it belongs to this layer. */
|
|
9
|
+
export function FieldHost({ mode }: { mode: 'inline' | 'popover' }) {
|
|
10
|
+
const { editing, editors, session, snap, targets, setEditing, addGhost, isLive, t } = useOnPage()
|
|
11
|
+
const def = editing ? editors[editing.editor] : undefined
|
|
12
|
+
const state = editing ? snap.docs[editing.key] : undefined
|
|
13
|
+
const key = editing?.key ?? ''
|
|
14
|
+
const path = editing?.path ?? ''
|
|
15
|
+
const live = isLive(key)
|
|
16
|
+
const clicked = editing?.el
|
|
17
|
+
|
|
18
|
+
const element = useCallback(
|
|
19
|
+
() => (clicked?.isConnected ? clicked : (targets.find(key, path)?.el ?? null)),
|
|
20
|
+
[clicked, targets, key, path],
|
|
21
|
+
)
|
|
22
|
+
const close = useCallback(() => {
|
|
23
|
+
session.cancelTransient(key)
|
|
24
|
+
setEditing(null)
|
|
25
|
+
}, [session, key, setEditing])
|
|
26
|
+
const commit = useCallback(
|
|
27
|
+
(value: unknown) => {
|
|
28
|
+
session.setValue(key, path, value)
|
|
29
|
+
if (!live) void session.flush() // refresh mode: show it as soon as possible
|
|
30
|
+
},
|
|
31
|
+
[session, key, path, live],
|
|
32
|
+
)
|
|
33
|
+
const preview = useCallback(
|
|
34
|
+
(value: unknown) => session.setValue(key, path, value, { transient: true }),
|
|
35
|
+
[session, key, path],
|
|
36
|
+
)
|
|
37
|
+
const ghost = useCallback<FieldEditorProps['ghost']>(
|
|
38
|
+
(g) => addGhost({ key, path, ...g }),
|
|
39
|
+
[addGhost, key, path],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if (!editing || !def || def.mode !== mode || !state?.schema || !state.doc) return null
|
|
43
|
+
const field = fieldAtPath(state.schema.fields, state.doc, path)?.field
|
|
44
|
+
if (!field) return null
|
|
45
|
+
const Editor = def.component
|
|
46
|
+
// Editors may be lazy (the rich-text editor is): render nothing until they've loaded.
|
|
47
|
+
return (
|
|
48
|
+
<Suspense fallback={null}>
|
|
49
|
+
<Editor
|
|
50
|
+
key={`${key}|${path}`}
|
|
51
|
+
docKey={key}
|
|
52
|
+
path={path}
|
|
53
|
+
field={field}
|
|
54
|
+
element={element}
|
|
55
|
+
value={getAt(state.doc, path)}
|
|
56
|
+
commit={commit}
|
|
57
|
+
preview={preview}
|
|
58
|
+
close={close}
|
|
59
|
+
ghost={ghost}
|
|
60
|
+
live={live}
|
|
61
|
+
point={editing.point}
|
|
62
|
+
t={t}
|
|
63
|
+
/>
|
|
64
|
+
</Suspense>
|
|
65
|
+
)
|
|
66
|
+
}
|