@payload-onpage/react 0.2.0 → 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/dist/OnPageClient.d.ts.map +1 -1
- package/dist/OnPageClient.js +9 -5
- package/dist/OnPageClient.js.map +1 -1
- package/dist/annotations.d.ts +1 -1
- package/dist/annotations.d.ts.map +1 -1
- package/dist/context.d.ts +19 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +31 -7
- package/dist/context.js.map +1 -1
- package/dist/dom.d.ts +16 -12
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +16 -16
- package/dist/dom.js.map +1 -1
- package/dist/editor/FieldHost.d.ts.map +1 -1
- package/dist/editor/FieldHost.js +18 -11
- package/dist/editor/FieldHost.js.map +1 -1
- package/dist/editor/OnPageEditor.d.ts +15 -8
- package/dist/editor/OnPageEditor.d.ts.map +1 -1
- package/dist/editor/OnPageEditor.js +73 -103
- package/dist/editor/OnPageEditor.js.map +1 -1
- package/dist/editor/editTarget.d.ts +28 -0
- package/dist/editor/editTarget.d.ts.map +1 -0
- package/dist/editor/editTarget.js +20 -0
- package/dist/editor/editTarget.js.map +1 -0
- package/dist/editor/useClientDocs.d.ts +4 -0
- package/dist/editor/useClientDocs.d.ts.map +1 -0
- package/dist/editor/useClientDocs.js +16 -0
- package/dist/editor/useClientDocs.js.map +1 -0
- package/dist/editor/useEditorSession.d.ts +13 -0
- package/dist/editor/useEditorSession.d.ts.map +1 -0
- package/dist/editor/useEditorSession.js +30 -0
- package/dist/editor/useEditorSession.js.map +1 -0
- package/dist/editor/useFlash.d.ts +4 -0
- package/dist/editor/useFlash.d.ts.map +1 -0
- package/dist/editor/useFlash.js +16 -0
- package/dist/editor/useFlash.js.map +1 -0
- package/dist/editor/useGhosts.d.ts +5 -7
- package/dist/editor/useGhosts.d.ts.map +1 -1
- package/dist/editor/useGhosts.js.map +1 -1
- package/dist/editor/usePageEvents.d.ts.map +1 -1
- package/dist/editor/usePageEvents.js +16 -16
- package/dist/editor/usePageEvents.js.map +1 -1
- package/dist/editor-kit.d.ts +10 -6
- package/dist/editor-kit.d.ts.map +1 -1
- package/dist/editor-kit.js +8 -5
- package/dist/editor-kit.js.map +1 -1
- package/dist/editors/CheckboxEditor.d.ts +6 -1
- package/dist/editors/CheckboxEditor.d.ts.map +1 -1
- package/dist/editors/CheckboxEditor.js +6 -3
- package/dist/editors/CheckboxEditor.js.map +1 -1
- package/dist/editors/ColorEditor.d.ts +3 -1
- package/dist/editors/ColorEditor.d.ts.map +1 -1
- package/dist/editors/ColorEditor.js +9 -15
- package/dist/editors/ColorEditor.js.map +1 -1
- package/dist/editors/DateEditor.d.ts +3 -1
- package/dist/editors/DateEditor.d.ts.map +1 -1
- package/dist/editors/DateEditor.js +10 -12
- package/dist/editors/DateEditor.js.map +1 -1
- package/dist/editors/InlineText.d.ts +2 -2
- package/dist/editors/InlineText.d.ts.map +1 -1
- package/dist/editors/InlineText.js +2 -3
- package/dist/editors/InlineText.js.map +1 -1
- package/dist/editors/NumberEditor.d.ts +3 -1
- package/dist/editors/NumberEditor.d.ts.map +1 -1
- package/dist/editors/NumberEditor.js +9 -17
- package/dist/editors/NumberEditor.js.map +1 -1
- package/dist/editors/RelationshipEditor.d.ts +7 -1
- package/dist/editors/RelationshipEditor.d.ts.map +1 -1
- package/dist/editors/RelationshipEditor.js +10 -18
- package/dist/editors/RelationshipEditor.js.map +1 -1
- package/dist/editors/SelectEditor.d.ts +4 -1
- package/dist/editors/SelectEditor.d.ts.map +1 -1
- package/dist/editors/SelectEditor.js +8 -13
- package/dist/editors/SelectEditor.js.map +1 -1
- package/dist/editors/UploadEditor.d.ts +7 -1
- package/dist/editors/UploadEditor.d.ts.map +1 -1
- package/dist/editors/UploadEditor.js +5 -5
- package/dist/editors/UploadEditor.js.map +1 -1
- package/dist/editors/index.d.ts +0 -1
- package/dist/editors/index.d.ts.map +1 -1
- package/dist/editors/index.js +56 -16
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/kit.d.ts +39 -0
- package/dist/editors/kit.d.ts.map +1 -0
- package/dist/editors/kit.js +44 -0
- package/dist/editors/kit.js.map +1 -0
- package/dist/editors/useDebouncedSearch.d.ts +10 -6
- package/dist/editors/useDebouncedSearch.d.ts.map +1 -1
- package/dist/editors/useDebouncedSearch.js +11 -7
- package/dist/editors/useDebouncedSearch.js.map +1 -1
- package/dist/geometry.d.ts +49 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +48 -0
- package/dist/geometry.js.map +1 -0
- package/dist/hub.d.ts +4 -4
- package/dist/hub.d.ts.map +1 -1
- package/dist/hub.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +4 -2
- package/dist/layout.js.map +1 -1
- package/dist/locators.d.ts +0 -2
- package/dist/locators.d.ts.map +1 -1
- package/dist/locators.js +10 -16
- package/dist/locators.js.map +1 -1
- package/dist/messages.d.ts +15 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +5 -0
- package/dist/messages.js.map +1 -1
- package/dist/registry.d.ts +21 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +31 -6
- package/dist/registry.js.map +1 -1
- package/dist/rowOps.d.ts +20 -0
- package/dist/rowOps.d.ts.map +1 -0
- package/dist/rowOps.js +48 -0
- package/dist/rowOps.js.map +1 -0
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +3 -0
- package/dist/sanitize.js.map +1 -1
- package/dist/settings.d.ts +10 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +30 -0
- package/dist/settings.js.map +1 -0
- package/dist/styles.d.ts +0 -4
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +2 -1
- package/dist/styles.js.map +1 -1
- package/dist/targets.d.ts +1 -1
- package/dist/targets.d.ts.map +1 -1
- package/dist/types.d.ts +71 -46
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/BlockPicker.d.ts.map +1 -1
- package/dist/ui/BlockPicker.js +6 -5
- package/dist/ui/BlockPicker.js.map +1 -1
- package/dist/ui/Dialog.d.ts +0 -5
- package/dist/ui/Dialog.d.ts.map +1 -1
- package/dist/ui/Dialog.js +10 -6
- package/dist/ui/Dialog.js.map +1 -1
- package/dist/ui/Dock.d.ts +1 -6
- package/dist/ui/Dock.d.ts.map +1 -1
- package/dist/ui/Dock.js +46 -28
- package/dist/ui/Dock.js.map +1 -1
- package/dist/ui/Overlay.d.ts.map +1 -1
- package/dist/ui/Overlay.js +39 -37
- package/dist/ui/Overlay.js.map +1 -1
- package/dist/ui/PreviewToolbar.d.ts +3 -3
- package/dist/ui/PreviewToolbar.d.ts.map +1 -1
- package/dist/ui/PreviewToolbar.js +4 -6
- package/dist/ui/PreviewToolbar.js.map +1 -1
- package/dist/ui/SettingsPanel.d.ts +0 -9
- package/dist/ui/SettingsPanel.d.ts.map +1 -1
- package/dist/ui/SettingsPanel.js +36 -41
- package/dist/ui/SettingsPanel.js.map +1 -1
- package/dist/ui/ToolbarButton.d.ts +12 -0
- package/dist/ui/ToolbarButton.d.ts.map +1 -0
- package/dist/ui/ToolbarButton.js +7 -0
- package/dist/ui/ToolbarButton.js.map +1 -0
- package/dist/useEvent.d.ts +1 -1
- package/dist/useEvent.d.ts.map +1 -1
- package/dist/useEvent.js +3 -1
- package/dist/useEvent.js.map +1 -1
- package/dist/useIsClient.d.ts +5 -0
- package/dist/useIsClient.d.ts.map +1 -0
- package/dist/useIsClient.js +8 -0
- package/dist/useIsClient.js.map +1 -0
- package/package.json +2 -2
- package/src/OnPageClient.tsx +9 -5
- package/src/annotations.ts +1 -1
- package/src/context.tsx +53 -10
- package/src/dom.ts +17 -18
- package/src/editor/FieldHost.tsx +26 -11
- package/src/editor/OnPageEditor.tsx +103 -160
- package/src/editor/editTarget.ts +41 -0
- package/src/editor/useClientDocs.ts +19 -0
- package/src/editor/useEditorSession.ts +36 -0
- package/src/editor/useFlash.ts +18 -0
- package/src/editor/useGhosts.ts +5 -7
- package/src/editor/usePageEvents.ts +13 -14
- package/src/editor-kit.ts +10 -6
- package/src/editors/CheckboxEditor.tsx +11 -12
- package/src/editors/ColorEditor.tsx +15 -35
- package/src/editors/DateEditor.tsx +18 -37
- package/src/editors/InlineText.tsx +4 -5
- package/src/editors/NumberEditor.tsx +15 -36
- package/src/editors/RelationshipEditor.tsx +18 -36
- package/src/editors/SelectEditor.tsx +14 -22
- package/src/editors/UploadEditor.tsx +6 -6
- package/src/editors/index.ts +56 -16
- package/src/editors/kit.tsx +97 -0
- package/src/editors/useDebouncedSearch.ts +18 -10
- package/src/geometry.ts +62 -0
- package/src/hub.ts +5 -5
- package/src/index.ts +5 -2
- package/src/layout.ts +5 -2
- package/src/locators.ts +11 -14
- package/src/messages.ts +26 -5
- package/src/registry.ts +42 -8
- package/src/rowOps.ts +64 -0
- package/src/sanitize.ts +6 -0
- package/src/settings.ts +49 -0
- package/src/styles.ts +2 -1
- package/src/targets.ts +1 -1
- package/src/types.ts +78 -45
- package/src/ui/BlockPicker.tsx +6 -7
- package/src/ui/Dialog.tsx +9 -5
- package/src/ui/Dock.tsx +67 -50
- package/src/ui/Overlay.tsx +75 -102
- package/src/ui/PreviewToolbar.tsx +7 -15
- package/src/ui/SettingsPanel.tsx +84 -81
- package/src/ui/ToolbarButton.tsx +36 -0
- package/src/useEvent.ts +4 -2
- package/src/useIsClient.ts +15 -0
package/src/ui/Dock.tsx
CHANGED
|
@@ -5,14 +5,49 @@
|
|
|
5
5
|
* recovery: reload after a conflict, retry after an error), undo/redo, locale,
|
|
6
6
|
* share link, preview, discard, admin, exit and publish.
|
|
7
7
|
*/
|
|
8
|
+
import { errorText } from '@payload-onpage/core'
|
|
8
9
|
import type { DocumentState } from '@payload-onpage/core'
|
|
9
10
|
import { useEffect, useState } from 'react'
|
|
10
11
|
import { useOnPage } from '../context.tsx'
|
|
11
|
-
import { relativeTime } from '../messages.ts'
|
|
12
|
+
import { relativeTime, type Translate } from '../messages.ts'
|
|
12
13
|
import { PostButton } from './PostButton.tsx'
|
|
14
|
+
import { ToolbarButton } from './ToolbarButton.tsx'
|
|
15
|
+
import type { Flash } from '../types.ts'
|
|
13
16
|
|
|
14
17
|
const title = (d: DocumentState) => d.schema?.labels.singular ?? d.ref.slug
|
|
15
18
|
|
|
19
|
+
interface DockState {
|
|
20
|
+
docs: DocumentState[]
|
|
21
|
+
ready: DocumentState[]
|
|
22
|
+
failed: DocumentState | undefined
|
|
23
|
+
conflict: DocumentState | undefined
|
|
24
|
+
error: DocumentState | undefined
|
|
25
|
+
saving: boolean
|
|
26
|
+
rendering: boolean
|
|
27
|
+
lastSaved: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What the dock says, most urgent first. */
|
|
31
|
+
function dockStatus(s: DockState, t: Translate): Flash {
|
|
32
|
+
if (s.failed)
|
|
33
|
+
return {
|
|
34
|
+
text: t('dock.unavailable', { doc: title(s.failed), error: s.failed.error ?? '' }),
|
|
35
|
+
bad: true,
|
|
36
|
+
}
|
|
37
|
+
if (!s.docs.length) return { text: t('dock.nothing') }
|
|
38
|
+
if (!s.ready.length) return { text: t('dock.loading') }
|
|
39
|
+
if (s.conflict) return { text: t('dock.conflict', { doc: title(s.conflict) }), bad: true }
|
|
40
|
+
if (s.error)
|
|
41
|
+
return {
|
|
42
|
+
text: t('dock.saveFailed', { doc: title(s.error), message: s.error.save.message ?? '' }),
|
|
43
|
+
bad: true,
|
|
44
|
+
}
|
|
45
|
+
if (s.saving) return { text: t('dock.saving') }
|
|
46
|
+
if (s.rendering) return { text: t('dock.updating') }
|
|
47
|
+
if (s.lastSaved) return { text: t('dock.saved', { ago: relativeTime(s.lastSaved) }) }
|
|
48
|
+
return { text: t('dock.editing') }
|
|
49
|
+
}
|
|
50
|
+
|
|
16
51
|
function useTicker(ms: number) {
|
|
17
52
|
const [, setN] = useState(0)
|
|
18
53
|
useEffect(() => {
|
|
@@ -21,8 +56,8 @@ function useTicker(ms: number) {
|
|
|
21
56
|
}, [ms])
|
|
22
57
|
}
|
|
23
58
|
|
|
24
|
-
export function Dock(
|
|
25
|
-
const { session, snap, links, rendering, notify, rerender, docKeys, t } = useOnPage()
|
|
59
|
+
export function Dock() {
|
|
60
|
+
const { session, snap, links, rendering, notify, rerender, docKeys, flash, t } = useOnPage()
|
|
26
61
|
const [confirmDiscard, setConfirmDiscard] = useState(false)
|
|
27
62
|
useTicker(5000)
|
|
28
63
|
useEffect(() => {
|
|
@@ -41,23 +76,11 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
41
76
|
const primary = docs[0]
|
|
42
77
|
const busy = snap.busy
|
|
43
78
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
? { text: t('dock.loading') }
|
|
50
|
-
: conflict
|
|
51
|
-
? { text: t('dock.conflict', { doc: title(conflict) }), bad: true }
|
|
52
|
-
: error
|
|
53
|
-
? { text: `${title(error)}: ${error.save.message ?? ''}`, bad: true }
|
|
54
|
-
: saving
|
|
55
|
-
? { text: t('dock.saving') }
|
|
56
|
-
: rendering
|
|
57
|
-
? { text: t('dock.updating') }
|
|
58
|
-
: lastSaved
|
|
59
|
-
? { text: t('dock.saved', { ago: relativeTime(lastSaved) }) }
|
|
60
|
-
: { text: t('dock.editing') }
|
|
79
|
+
const retryable = failed ?? error
|
|
80
|
+
const status = dockStatus(
|
|
81
|
+
{ docs, ready, failed, conflict, error, saving, rendering, lastSaved },
|
|
82
|
+
t,
|
|
83
|
+
)
|
|
61
84
|
|
|
62
85
|
const schema = ready[0]?.schema
|
|
63
86
|
const nonDefaultLocale =
|
|
@@ -67,10 +90,10 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
67
90
|
try {
|
|
68
91
|
const res = await session.publish({ keys: docKeys })
|
|
69
92
|
if (res.published.length) await rerender(res.published)
|
|
70
|
-
if (res.ok) notify(res.published.length ? 'notify.published' : 'notify.nothingToPublish')
|
|
71
|
-
else notify('notify.publishFailed', { message: res.message ?? '' }, true)
|
|
93
|
+
if (res.ok) notify(t(res.published.length ? 'notify.published' : 'notify.nothingToPublish'))
|
|
94
|
+
else notify(t('notify.publishFailed', { message: res.message ?? '' }), true)
|
|
72
95
|
} catch (e) {
|
|
73
|
-
notify('notify.publishFailed', { message: (e
|
|
96
|
+
notify(t('notify.publishFailed', { message: errorText(e) }), true)
|
|
74
97
|
}
|
|
75
98
|
}
|
|
76
99
|
const discard = async () => {
|
|
@@ -79,9 +102,9 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
79
102
|
setConfirmDiscard(false)
|
|
80
103
|
try {
|
|
81
104
|
await session.discard({ keys: docKeys })
|
|
82
|
-
notify('notify.discarded')
|
|
105
|
+
notify(t('notify.discarded'))
|
|
83
106
|
} catch (e) {
|
|
84
|
-
notify((e
|
|
107
|
+
notify(errorText(e), true)
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
const reload = async (d: DocumentState) => {
|
|
@@ -89,7 +112,7 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
89
112
|
await session.reload(d.key)
|
|
90
113
|
await rerender([d.ref])
|
|
91
114
|
} catch (e) {
|
|
92
|
-
notify((e
|
|
115
|
+
notify(errorText(e), true)
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
const createLink = session.dataSource.createPreviewLink?.bind(session.dataSource)
|
|
@@ -98,9 +121,9 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
98
121
|
try {
|
|
99
122
|
const { url, expiresAt } = await createLink(primary.ref, { locale: session.locale })
|
|
100
123
|
await navigator.clipboard.writeText(url)
|
|
101
|
-
notify('notify.linkCopied', { date: new Date(expiresAt).toLocaleString() })
|
|
124
|
+
notify(t('notify.linkCopied', { date: new Date(expiresAt).toLocaleString() }))
|
|
102
125
|
} catch (e) {
|
|
103
|
-
notify('notify.linkFailed', { message: (e
|
|
126
|
+
notify(t('notify.linkFailed', { message: errorText(e) }), true)
|
|
104
127
|
}
|
|
105
128
|
}
|
|
106
129
|
|
|
@@ -136,11 +159,11 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
136
159
|
{t('dock.reload')}
|
|
137
160
|
</button>
|
|
138
161
|
)}
|
|
139
|
-
{
|
|
162
|
+
{retryable && (
|
|
140
163
|
<button
|
|
141
164
|
type="button"
|
|
142
165
|
className="po-btn"
|
|
143
|
-
onClick={() => void session.retry(
|
|
166
|
+
onClick={() => void session.retry(retryable.key)}
|
|
144
167
|
data-testid="onpage-retry"
|
|
145
168
|
>
|
|
146
169
|
{t('dock.retry')}
|
|
@@ -157,28 +180,22 @@ export function Dock({ flash }: { flash: { text: string; bad?: boolean } | null
|
|
|
157
180
|
)}
|
|
158
181
|
{session.locale && <span className="po-chip-locale">{session.locale}</span>}
|
|
159
182
|
<span className="po-sep" />
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
183
|
+
<ToolbarButton
|
|
184
|
+
label={t('dock.undo')}
|
|
185
|
+
hint={`${t('dock.undo')} (⌘Z)`}
|
|
186
|
+
icon="↶"
|
|
164
187
|
disabled={!snap.canUndo}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
type="button"
|
|
173
|
-
className="po-btn"
|
|
174
|
-
onClick={() => session.redo()}
|
|
188
|
+
onClick={() => session.undo()}
|
|
189
|
+
testId="onpage-undo"
|
|
190
|
+
/>
|
|
191
|
+
<ToolbarButton
|
|
192
|
+
label={t('dock.redo')}
|
|
193
|
+
hint={`${t('dock.redo')} (⇧⌘Z)`}
|
|
194
|
+
icon="↷"
|
|
175
195
|
disabled={!snap.canRedo}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
>
|
|
180
|
-
↷
|
|
181
|
-
</button>
|
|
196
|
+
onClick={() => session.redo()}
|
|
197
|
+
testId="onpage-redo"
|
|
198
|
+
/>
|
|
182
199
|
<span className="po-sep" />
|
|
183
200
|
{links.mode && (
|
|
184
201
|
<PostButton
|
package/src/ui/Overlay.tsx
CHANGED
|
@@ -6,35 +6,34 @@
|
|
|
6
6
|
* from the target elements whenever the layout changes.
|
|
7
7
|
*/
|
|
8
8
|
import {
|
|
9
|
-
cloneRow,
|
|
10
9
|
fieldAtPath,
|
|
11
10
|
getAt,
|
|
12
|
-
insertItem,
|
|
13
11
|
isEditablePath,
|
|
14
|
-
|
|
12
|
+
isListField,
|
|
15
13
|
newArrayRow,
|
|
16
14
|
refKey,
|
|
17
|
-
removeItem,
|
|
18
15
|
resolveRow,
|
|
19
|
-
|
|
20
|
-
type Doc,
|
|
16
|
+
rowPrefixes,
|
|
21
17
|
type RowInfo,
|
|
22
18
|
type Target,
|
|
19
|
+
isReady,
|
|
20
|
+
type RefKey,
|
|
23
21
|
} from '@payload-onpage/core'
|
|
24
22
|
import { useEffect, useState } from 'react'
|
|
25
23
|
import { useOnPage } from '../context.tsx'
|
|
26
24
|
import { isOurs } from '../dom.ts'
|
|
25
|
+
import { belowList, GEOMETRY, inViewport, outlineBox, toolbarPosition } from '../geometry.ts'
|
|
27
26
|
import { useLayoutTick } from '../layout.ts'
|
|
27
|
+
import { ToolbarButton } from './ToolbarButton.tsx'
|
|
28
28
|
import { targetsAt } from '../targets.ts'
|
|
29
|
-
import {
|
|
29
|
+
import { insertRow, rowOps } from '../rowOps.ts'
|
|
30
|
+
import { hasSettings } from '../settings.ts'
|
|
30
31
|
|
|
31
32
|
interface Hover {
|
|
32
|
-
field?: Target
|
|
33
|
+
field?: Target | undefined
|
|
33
34
|
rows: Target[] // innermost first
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const VIEW_PAD = 8
|
|
37
|
-
|
|
38
37
|
function useHover(): Hover {
|
|
39
38
|
const { targets } = useOnPage()
|
|
40
39
|
const [hover, setHover] = useState<Hover>({ rows: [] })
|
|
@@ -70,14 +69,14 @@ function Outline({
|
|
|
70
69
|
if (r.width === 0 && r.height === 0) return null
|
|
71
70
|
return (
|
|
72
71
|
<>
|
|
73
|
-
<div
|
|
74
|
-
className={`po-outline${variant ? ` is-${variant}` : ''}`}
|
|
75
|
-
style={{ top: r.top - 2, left: r.left - 2, width: r.width + 4, height: r.height + 4 }}
|
|
76
|
-
/>
|
|
72
|
+
<div className={`po-outline${variant ? ` is-${variant}` : ''}`} style={outlineBox(r)} />
|
|
77
73
|
{label && (
|
|
78
74
|
<div
|
|
79
75
|
className={`po-chip${variant === 'item' ? ' is-item' : ''}`}
|
|
80
|
-
style={{
|
|
76
|
+
style={{
|
|
77
|
+
top: Math.max(r.top - GEOMETRY.outline, GEOMETRY.chipMinTop),
|
|
78
|
+
left: r.left - GEOMETRY.outline,
|
|
79
|
+
}}
|
|
81
80
|
>
|
|
82
81
|
{label}
|
|
83
82
|
</div>
|
|
@@ -87,7 +86,7 @@ function Outline({
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
interface ActiveRow {
|
|
90
|
-
key:
|
|
89
|
+
key: RefKey
|
|
91
90
|
info: RowInfo
|
|
92
91
|
el: Element
|
|
93
92
|
}
|
|
@@ -96,94 +95,67 @@ function RowToolbar({ row, variant }: { row: ActiveRow; variant: 'block' | 'item
|
|
|
96
95
|
const { session, setSelected, setPanel, selected, targets, t } = useOnPage()
|
|
97
96
|
const { key, info, el } = row
|
|
98
97
|
const r = el.getBoundingClientRect()
|
|
99
|
-
if (r
|
|
100
|
-
const
|
|
101
|
-
const right = Math.max(window.innerWidth - r.right + VIEW_PAD, VIEW_PAD)
|
|
102
|
-
const { listPath, index, length, rowFields, path } = info
|
|
98
|
+
if (!inViewport(r, GEOMETRY.toolbar)) return null
|
|
99
|
+
const { path } = info
|
|
103
100
|
const label = targets.find(key, path, ['item'])?.label || info.label
|
|
104
|
-
const
|
|
105
|
-
const max = info.listField.maxRows
|
|
106
|
-
const min = info.listField.minRows ?? 0
|
|
107
|
-
|
|
101
|
+
const ops = rowOps(session, key, info)
|
|
108
102
|
const move = (dir: -1 | 1) => {
|
|
109
|
-
|
|
110
|
-
setSelected(
|
|
103
|
+
const next = ops.move(dir)
|
|
104
|
+
if (next) setSelected(next)
|
|
111
105
|
}
|
|
112
106
|
const duplicate = () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
setSelected({ key, path: `${listPath}.${index + 1}` })
|
|
107
|
+
const next = ops.duplicate()
|
|
108
|
+
if (next) setSelected(next)
|
|
117
109
|
}
|
|
118
110
|
const remove = () => {
|
|
119
|
-
|
|
120
|
-
if (selected?.key === key && selected.path.startsWith(path)) setSelected(null)
|
|
111
|
+
if (ops.remove() && selected?.key === key && selected.path.startsWith(path)) setSelected(null)
|
|
121
112
|
}
|
|
122
113
|
return (
|
|
123
114
|
<div
|
|
124
115
|
className={`po-bar${variant === 'item' ? ' is-item' : ''}`}
|
|
125
|
-
style={
|
|
116
|
+
style={toolbarPosition(r)}
|
|
126
117
|
role="toolbar"
|
|
127
118
|
aria-label={t('row.toolbar', { label })}
|
|
128
119
|
data-testid={`onpage-${variant}-toolbar`}
|
|
129
120
|
>
|
|
130
121
|
<span className="po-btn po-bar-label">{label}</span>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
disabled={
|
|
122
|
+
<ToolbarButton
|
|
123
|
+
label={t('row.moveUp')}
|
|
124
|
+
icon="↑"
|
|
125
|
+
disabled={!ops.canMove(-1)}
|
|
135
126
|
onClick={() => move(-1)}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<button
|
|
143
|
-
type="button"
|
|
144
|
-
className="po-btn"
|
|
145
|
-
disabled={index >= length - 1}
|
|
127
|
+
testId={`onpage-${variant}-up`}
|
|
128
|
+
/>
|
|
129
|
+
<ToolbarButton
|
|
130
|
+
label={t('row.moveDown')}
|
|
131
|
+
icon="↓"
|
|
132
|
+
disabled={!ops.canMove(1)}
|
|
146
133
|
onClick={() => move(1)}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<button
|
|
154
|
-
type="button"
|
|
155
|
-
className="po-btn"
|
|
156
|
-
disabled={max !== undefined && length >= max}
|
|
134
|
+
testId={`onpage-${variant}-down`}
|
|
135
|
+
/>
|
|
136
|
+
<ToolbarButton
|
|
137
|
+
label={t('row.duplicate')}
|
|
138
|
+
icon="⧉"
|
|
139
|
+
disabled={!ops.canDuplicate}
|
|
157
140
|
onClick={duplicate}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{hasSettings(rowFields, path, key, targets) && (
|
|
165
|
-
<button
|
|
166
|
-
type="button"
|
|
167
|
-
className="po-btn"
|
|
141
|
+
testId={`onpage-${variant}-duplicate`}
|
|
142
|
+
/>
|
|
143
|
+
{hasSettings(info.rowFields, path, key, targets) && (
|
|
144
|
+
<ToolbarButton
|
|
145
|
+
label={t('row.settings')}
|
|
146
|
+
icon="⚙"
|
|
168
147
|
onClick={() => setPanel({ kind: 'settings', key, path, title: label })}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
data-testid={`onpage-${variant}-settings`}
|
|
172
|
-
>
|
|
173
|
-
⚙
|
|
174
|
-
</button>
|
|
148
|
+
testId={`onpage-${variant}-settings`}
|
|
149
|
+
/>
|
|
175
150
|
)}
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
151
|
+
<ToolbarButton
|
|
152
|
+
label={t('row.delete')}
|
|
153
|
+
icon="✕"
|
|
154
|
+
danger
|
|
155
|
+
disabled={!ops.canRemove}
|
|
180
156
|
onClick={remove}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
data-testid={`onpage-${variant}-delete`}
|
|
184
|
-
>
|
|
185
|
-
✕
|
|
186
|
-
</button>
|
|
157
|
+
testId={`onpage-${variant}-delete`}
|
|
158
|
+
/>
|
|
187
159
|
</div>
|
|
188
160
|
)
|
|
189
161
|
}
|
|
@@ -201,7 +173,7 @@ function InsertPoints({ row }: { row: ActiveRow }) {
|
|
|
201
173
|
onClick={() =>
|
|
202
174
|
setPanel({ kind: 'blocks', key: row.key, listPath: row.info.listPath, index })
|
|
203
175
|
}
|
|
204
|
-
aria-label={
|
|
176
|
+
aria-label={t(edge === 'above' ? 'row.addSectionAbove' : 'row.addSectionBelow')}
|
|
205
177
|
data-testid={`onpage-insert-${edge}`}
|
|
206
178
|
>
|
|
207
179
|
+ {t('row.addSection')}
|
|
@@ -220,10 +192,11 @@ function AddItem({ list }: { list: Target }) {
|
|
|
220
192
|
const { snap, session, setPanel, setSelected, t } = useOnPage()
|
|
221
193
|
const key = refKey(list.ref)
|
|
222
194
|
const state = snap.docs[key]
|
|
223
|
-
if (!state
|
|
195
|
+
if (!isReady(state) || !isEditablePath(state.schema, list.path)) return null
|
|
224
196
|
const field = fieldAtPath(state.schema.fields, state.doc, list.path)?.field
|
|
225
|
-
if (!field || (field
|
|
226
|
-
const
|
|
197
|
+
if (!field || !isListField(field)) return null
|
|
198
|
+
const rows = getAt(state.doc, list.path)
|
|
199
|
+
const length = Array.isArray(rows) ? rows.length : 0
|
|
227
200
|
const r = list.el.getBoundingClientRect()
|
|
228
201
|
if (field.type === 'blocks') {
|
|
229
202
|
// Sections get "+ Add section" above/below each one; this covers an empty list.
|
|
@@ -231,7 +204,10 @@ function AddItem({ list }: { list: Target }) {
|
|
|
231
204
|
return (
|
|
232
205
|
<div
|
|
233
206
|
className="po-add"
|
|
234
|
-
style={{
|
|
207
|
+
style={{
|
|
208
|
+
top: r.top + Math.min(r.height / 2, GEOMETRY.emptyListOffset),
|
|
209
|
+
left: r.left + r.width / 2,
|
|
210
|
+
}}
|
|
235
211
|
>
|
|
236
212
|
<button
|
|
237
213
|
type="button"
|
|
@@ -246,13 +222,10 @@ function AddItem({ list }: { list: Target }) {
|
|
|
246
222
|
}
|
|
247
223
|
if (field.maxRows !== undefined && length >= field.maxRows) return null
|
|
248
224
|
// Below the list, or pinned to the bottom of the viewport while the list runs past it.
|
|
249
|
-
if (r
|
|
250
|
-
const y =
|
|
225
|
+
if (!inViewport(r) || r.top > window.innerHeight - GEOMETRY.toolbar) return null
|
|
226
|
+
const y = belowList(r)
|
|
251
227
|
const item = list.label || field.labels?.singular || field.label
|
|
252
|
-
const add = () =>
|
|
253
|
-
session.apply(key, (d) => updateArray(d, list.path, (a) => [...a, newArrayRow(field)]))
|
|
254
|
-
setSelected({ key, path: `${list.path}.${length}` })
|
|
255
|
-
}
|
|
228
|
+
const add = () => setSelected(insertRow(session, key, list.path, length, newArrayRow(field)))
|
|
256
229
|
return (
|
|
257
230
|
<div className="po-add is-item" style={{ top: y, left: r.left + r.width / 2 }}>
|
|
258
231
|
<button type="button" className="po-btn" onClick={add} data-testid="onpage-add-item">
|
|
@@ -271,8 +244,7 @@ export function Overlay() {
|
|
|
271
244
|
if (!target) return null
|
|
272
245
|
const key = refKey(target.ref)
|
|
273
246
|
const state = snap.docs[key]
|
|
274
|
-
const info =
|
|
275
|
-
state?.schema && state.doc ? resolveRow(state.schema, state.doc, target.path) : null
|
|
247
|
+
const info = isReady(state) ? resolveRow(state.schema, state.doc, target.path) : null
|
|
276
248
|
return info ? { key, info, el: target.el } : null
|
|
277
249
|
}
|
|
278
250
|
|
|
@@ -284,13 +256,13 @@ export function Overlay() {
|
|
|
284
256
|
// Its section: the outermost enclosing row of a blocks field (may be itself).
|
|
285
257
|
let section: ActiveRow | null = null
|
|
286
258
|
if (active) {
|
|
287
|
-
const
|
|
288
|
-
for (let i = 1; i < segs.length && !section; i++) {
|
|
289
|
-
if (!/^\d+$/.test(segs[i])) continue
|
|
290
|
-
const path = segs.slice(0, i + 1).join('.')
|
|
259
|
+
for (const path of [...rowPrefixes(active.info.path), active.info.path]) {
|
|
291
260
|
const candidate =
|
|
292
261
|
path === active.info.path ? active : rowFor(targets.find(active.key, path, ['item']))
|
|
293
|
-
if (candidate?.info.isBlock)
|
|
262
|
+
if (candidate?.info.isBlock) {
|
|
263
|
+
section = candidate
|
|
264
|
+
break
|
|
265
|
+
}
|
|
294
266
|
}
|
|
295
267
|
}
|
|
296
268
|
const item = active && section?.info.path !== active.info.path ? active : null
|
|
@@ -302,7 +274,8 @@ export function Overlay() {
|
|
|
302
274
|
: undefined
|
|
303
275
|
const emptyLists = lists.filter((l) => {
|
|
304
276
|
const doc = snap.docs[refKey(l.ref)]?.doc
|
|
305
|
-
|
|
277
|
+
const rows = doc ? getAt(doc, l.path) : undefined
|
|
278
|
+
return doc && !(Array.isArray(rows) && rows.length)
|
|
306
279
|
})
|
|
307
280
|
const hoverField = !editing ? hover.field : undefined
|
|
308
281
|
|
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* page in a framed viewport. Signed-in editors can switch back to editing;
|
|
6
6
|
* people on a shared link can leave to the published page.
|
|
7
7
|
*/
|
|
8
|
-
import { useEffect, useMemo, useState
|
|
8
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
9
|
+
import { useEditorStyles, useIsClient } from '../useIsClient.ts'
|
|
9
10
|
import { createPortal } from 'react-dom'
|
|
10
11
|
import { UI } from '../dom.ts'
|
|
11
12
|
import { createTranslate, type Messages } from '../messages.ts'
|
|
12
|
-
import { injectStyles } from '../styles.ts'
|
|
13
13
|
import { PostButton } from './PostButton.tsx'
|
|
14
14
|
|
|
15
15
|
export interface PreviewToolbarProps {
|
|
16
16
|
/** Endpoint that leaves preview (POST), back to the published page. */
|
|
17
17
|
exitHref: string
|
|
18
18
|
/** Endpoint that switches to editing (POST); omit for people who can't edit (shared links). */
|
|
19
|
-
editHref?: string
|
|
19
|
+
editHref?: string | undefined
|
|
20
20
|
/** Opened from a shared preview link. */
|
|
21
|
-
shared?: boolean
|
|
22
|
-
messages?: Messages
|
|
21
|
+
shared?: boolean | undefined
|
|
22
|
+
messages?: Messages | undefined
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const DEVICES = [
|
|
@@ -34,19 +34,11 @@ export const inPreviewFrame = (): boolean =>
|
|
|
34
34
|
typeof window !== 'undefined' &&
|
|
35
35
|
(window.self !== window.top || new URLSearchParams(window.location.search).has(FRAME_PARAM))
|
|
36
36
|
|
|
37
|
-
const noop = () => () => {}
|
|
38
|
-
const useMounted = () =>
|
|
39
|
-
useSyncExternalStore(
|
|
40
|
-
noop,
|
|
41
|
-
() => true,
|
|
42
|
-
() => false,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
37
|
export function PreviewToolbar({ exitHref, editHref, shared, messages }: PreviewToolbarProps) {
|
|
46
38
|
const [device, setDevice] = useState<(typeof DEVICES)[number] | null>(null)
|
|
47
|
-
const mounted =
|
|
39
|
+
const mounted = useIsClient()
|
|
48
40
|
const t = useMemo(() => createTranslate(messages), [messages])
|
|
49
|
-
|
|
41
|
+
useEditorStyles()
|
|
50
42
|
useEffect(() => {
|
|
51
43
|
if (!device) return
|
|
52
44
|
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setDevice(null)
|