@agent-native/core 0.157.3 → 0.157.5
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +40 -1
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -7
- package/corpus/templates/clips/actions/approve-recording-access-request.ts +147 -0
- package/corpus/templates/clips/actions/request-recording-access.ts +121 -23
- package/corpus/templates/clips/actions/view-screen.ts +1 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +34 -53
- package/corpus/templates/clips/app/components/player/request-access-dialog.tsx +104 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +36 -45
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +26 -1
- package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +123 -37
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +41 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -0
- package/corpus/templates/clips/server/lib/emails.ts +40 -11
- package/corpus/templates/clips/shared/recording-link.ts +32 -1
- package/corpus/templates/clips/shared/share-attribution.ts +13 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +22 -10
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +2 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +245 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +185 -134
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +61 -45
- package/corpus/templates/content/app/components/editor/extensions/ImageNode.ts +6 -0
- package/corpus/templates/content/app/components/editor/image-upload.ts +137 -5
- package/corpus/templates/content/app/global.css +8 -0
- package/corpus/templates/content/changelog/2026-08-12-image-uploads-now-confirm-the-image-can-be-displayed-before-.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-fill-the-page-width-by-default-and-remain-visible-whe.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-inserted-from-the-slash-command-stay-saved-after-uplo.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-svg-files-can-be-selected-reliably-from-the-image-picker.md +6 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -1
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +25 -30
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +124 -180
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +1 -0
- package/corpus/templates/slides/app/global.css +99 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/chat-threads/schema.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +318 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +41 -3
- package/dist/client/AssistantChat.d.ts +9 -1
- package/dist/client/AssistantChat.js +26 -13
- package/dist/client/MultiTabAssistantChat.js +18 -5
- package/dist/client/agent-chat.d.ts +8 -0
- package/dist/client/agent-chat.js +15 -0
- package/dist/client/app-chat-sidebar.d.ts +23 -0
- package/dist/client/app-chat-sidebar.js +53 -0
- package/dist/client/app-providers.js +31 -2
- package/dist/client/frame-protocol.d.ts +20 -2
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/sharing/AgentShareSection.d.ts +8 -0
- package/dist/client/sharing/AgentShareSection.js +50 -0
- package/dist/client/sharing/ShareButton.js +2 -1
- package/dist/client/sharing/ShareDialog.d.ts +2 -2
- package/dist/client/sharing/ShareDialog.js +21 -29
- package/dist/client/sharing/index.d.ts +1 -0
- package/dist/client/sharing/index.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +2 -1
- package/dist/client/theme.d.ts +21 -0
- package/dist/client/theme.js +60 -0
- package/dist/client/ui/index.d.ts +1 -1
- package/dist/client/ui/index.js +1 -1
- package/dist/client/use-session.js +23 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/dashboard-storage/schema.d.ts +1 -1
- package/dist/data-programs/schema.d.ts +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/server/auth.js +8 -5
- package/dist/server/identity-sso-store.d.ts +6 -1
- package/dist/server/identity-sso-store.js +57 -14
- package/dist/server/identity-sso.d.ts +7 -8
- package/dist/server/identity-sso.js +15 -12
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +1 -0
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/sharing/schema.js +4 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/actions-access-control.mdx +226 -0
- package/docs/content/actions-advanced.mdx +152 -0
- package/docs/content/actions-defining.mdx +275 -0
- package/docs/content/actions-other-surfaces.mdx +167 -0
- package/docs/content/actions-overview.mdx +214 -0
- package/docs/content/actions-run-context.mdx +180 -0
- package/docs/content/agent-surfaces.mdx +1 -1
- package/docs/content/agent-teams.mdx +1 -1
- package/docs/content/agent-web-surfaces.mdx +1 -1
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/automation-connectors.mdx +1 -1
- package/docs/content/automations.mdx +1 -1
- package/docs/content/blueprint-installer.mdx +1 -1
- package/docs/content/cli-adapters.mdx +1 -1
- package/docs/content/client-agent-chat.mdx +1 -1
- package/docs/content/client-data.mdx +2 -2
- package/docs/content/client-overview.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- package/docs/content/cross-app-sso.mdx +24 -17
- package/docs/content/data-programs.mdx +2 -2
- package/docs/content/database.mdx +1 -1
- package/docs/content/deployment.mdx +1 -1
- package/docs/content/docs-components.mdx +14 -3
- package/docs/content/drop-in-agent.mdx +2 -2
- package/docs/content/durable-resume.mdx +1 -1
- package/docs/content/evals.mdx +1 -1
- package/docs/content/extensions.mdx +1 -1
- package/docs/content/external-agents-catalog.mdx +2 -2
- package/docs/content/external-agents.mdx +1 -1
- package/docs/content/generative-ui.mdx +1 -1
- package/docs/content/getting-started.mdx +1 -1
- package/docs/content/http-api.mdx +4 -4
- package/docs/content/human-approval.mdx +1 -1
- package/docs/content/key-concepts.mdx +1 -1
- package/docs/content/mcp-apps.mdx +1 -1
- package/docs/content/mcp-clients.mdx +1 -1
- package/docs/content/mcp-protocol.mdx +1 -1
- package/docs/content/native-chat-ui.mdx +1 -1
- package/docs/content/observability.mdx +1 -1
- package/docs/content/organizations-teams-permissions.mdx +1 -1
- package/docs/content/processors.mdx +1 -1
- package/docs/content/pure-agent-apps.mdx +1 -1
- package/docs/content/recurring-jobs.mdx +1 -1
- package/docs/content/server-database.mdx +1 -1
- package/docs/content/server-overview.mdx +3 -3
- package/docs/content/server-routes.mdx +2 -2
- package/docs/content/template-analytics-developers.mdx +1 -1
- package/docs/content/template-assets-developers.mdx +1 -1
- package/docs/content/template-brain-developers.mdx +1 -1
- package/docs/content/template-calendar-developers.mdx +1 -1
- package/docs/content/template-chat-first-edits.mdx +1 -1
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/template-forms-developers.mdx +1 -1
- package/docs/content/template-forms.mdx +1 -1
- package/docs/content/template-mail-developers.mdx +2 -2
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/tracking.mdx +1 -1
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/actions.mdx +0 -750
|
@@ -52,12 +52,39 @@ const CLIPS_SENDER =
|
|
|
52
52
|
'From is the configured EMAIL_FROM with the display name "Agent-Native Clips"; on first-party agent-native.com deployments it becomes clips@agent-native.com. Reply-to is hello@agent-native.com.';
|
|
53
53
|
|
|
54
54
|
let registered = false;
|
|
55
|
+
const GLOBAL_REGISTRATION_KEY =
|
|
56
|
+
"__agentNativeClipsTransactionalEmailCatalogRegistered";
|
|
57
|
+
const globalRegistrationState = globalThis as typeof globalThis &
|
|
58
|
+
Record<string, boolean | undefined>;
|
|
59
|
+
|
|
60
|
+
function defineClipsTransactionalEmail(
|
|
61
|
+
definition: Parameters<typeof defineTransactionalEmail>[0],
|
|
62
|
+
): boolean {
|
|
63
|
+
try {
|
|
64
|
+
defineTransactionalEmail(definition);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (
|
|
68
|
+
error instanceof Error &&
|
|
69
|
+
error.message.startsWith('Duplicate transactional email id "clips.')
|
|
70
|
+
) {
|
|
71
|
+
// The same Clips catalog can be evaluated twice by Nitro/Vite. The
|
|
72
|
+
// first definition is canonical, so a duplicate is already registered.
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
55
78
|
|
|
56
79
|
export function registerClipsEmails(): void {
|
|
57
|
-
|
|
80
|
+
// Nitro/Vite can evaluate the same server module through two module
|
|
81
|
+
// instances during dev reloads. Keep the catalog registration process-wide
|
|
82
|
+
// so those instances cannot submit the same id twice.
|
|
83
|
+
if (registered || globalRegistrationState[GLOBAL_REGISTRATION_KEY]) return;
|
|
58
84
|
registered = true;
|
|
85
|
+
globalRegistrationState[GLOBAL_REGISTRATION_KEY] = true;
|
|
59
86
|
|
|
60
|
-
|
|
87
|
+
defineClipsTransactionalEmail({
|
|
61
88
|
id: CLIPS_ACCESS_REQUEST_EMAIL_ID,
|
|
62
89
|
name: "Clip access request",
|
|
63
90
|
trigger:
|
|
@@ -73,10 +100,12 @@ export function registerClipsEmails(): void {
|
|
|
73
100
|
requesterEmail: "sam.rivera@example.com",
|
|
74
101
|
recordingTitle: SAMPLE_TITLE,
|
|
75
102
|
url: "https://example.com/share/rec_sample",
|
|
103
|
+
allowAccessUrl:
|
|
104
|
+
"https://example.com/access-request/approve?recordingId=rec_sample&token=preview-token",
|
|
76
105
|
}),
|
|
77
106
|
});
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
defineClipsTransactionalEmail({
|
|
80
109
|
id: CLIPS_FIRST_VIEW_EMAIL_ID,
|
|
81
110
|
name: "First view on a Clip",
|
|
82
111
|
trigger:
|
|
@@ -96,7 +125,7 @@ export function registerClipsEmails(): void {
|
|
|
96
125
|
}),
|
|
97
126
|
});
|
|
98
127
|
|
|
99
|
-
|
|
128
|
+
defineClipsTransactionalEmail({
|
|
100
129
|
id: CLIPS_UNVIEWED_REMINDER_EMAIL_ID,
|
|
101
130
|
name: "Unviewed Clip reminder",
|
|
102
131
|
trigger:
|
|
@@ -118,7 +147,7 @@ export function registerClipsEmails(): void {
|
|
|
118
147
|
}),
|
|
119
148
|
});
|
|
120
149
|
|
|
121
|
-
|
|
150
|
+
defineClipsTransactionalEmail({
|
|
122
151
|
id: CLIPS_FIRST_AGENT_VIEW_EMAIL_ID,
|
|
123
152
|
name: "First agent read of a Clip",
|
|
124
153
|
trigger:
|
|
@@ -138,7 +167,7 @@ export function registerClipsEmails(): void {
|
|
|
138
167
|
}),
|
|
139
168
|
});
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
defineClipsTransactionalEmail({
|
|
142
171
|
id: CLIPS_FIRST_IMPORT_EMAIL_ID,
|
|
143
172
|
name: "First imported video is ready",
|
|
144
173
|
trigger:
|
|
@@ -157,7 +186,7 @@ export function registerClipsEmails(): void {
|
|
|
157
186
|
}),
|
|
158
187
|
});
|
|
159
188
|
|
|
160
|
-
|
|
189
|
+
defineClipsTransactionalEmail({
|
|
161
190
|
id: CLIPS_MONTHLY_RECAP_EMAIL_ID,
|
|
162
191
|
name: "Monthly Clips recap",
|
|
163
192
|
trigger:
|
|
@@ -191,7 +220,7 @@ export function registerClipsEmails(): void {
|
|
|
191
220
|
}),
|
|
192
221
|
});
|
|
193
222
|
|
|
194
|
-
|
|
223
|
+
defineClipsTransactionalEmail({
|
|
195
224
|
id: CLIPS_TWO_CLIPS_EMAIL_ID,
|
|
196
225
|
name: "Two Clips received",
|
|
197
226
|
trigger:
|
|
@@ -210,7 +239,7 @@ export function registerClipsEmails(): void {
|
|
|
210
239
|
}),
|
|
211
240
|
});
|
|
212
241
|
|
|
213
|
-
|
|
242
|
+
defineClipsTransactionalEmail({
|
|
214
243
|
id: CLIPS_ACTIVITY_COMMENT_EMAIL_ID,
|
|
215
244
|
name: "Clip comment",
|
|
216
245
|
trigger:
|
|
@@ -234,7 +263,7 @@ export function registerClipsEmails(): void {
|
|
|
234
263
|
}),
|
|
235
264
|
});
|
|
236
265
|
|
|
237
|
-
|
|
266
|
+
defineClipsTransactionalEmail({
|
|
238
267
|
id: CLIPS_ACTIVITY_REACTION_EMAIL_ID,
|
|
239
268
|
name: "Clip reaction",
|
|
240
269
|
trigger: "A viewer reacts with an emoji on a Clip.",
|
|
@@ -256,7 +285,7 @@ export function registerClipsEmails(): void {
|
|
|
256
285
|
}),
|
|
257
286
|
});
|
|
258
287
|
|
|
259
|
-
|
|
288
|
+
defineClipsTransactionalEmail({
|
|
260
289
|
id: CLIPS_ORGANIZATION_INVITE_EMAIL_ID,
|
|
261
290
|
name: "Organization invitation",
|
|
262
291
|
trigger:
|
|
@@ -12,15 +12,46 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { withShareAttribution } from "./share-attribution";
|
|
14
14
|
|
|
15
|
-
/** Short-lived capability used by a
|
|
15
|
+
/** Short-lived capability used by a private share viewer to request access. */
|
|
16
16
|
export const CLIPS_ACCESS_REQUEST_TOKEN_PREFIX = "clips-access-request";
|
|
17
17
|
export const CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS = 10 * 60;
|
|
18
18
|
|
|
19
|
+
/** Signed capability embedded in the owner's access-request email. */
|
|
20
|
+
export const CLIPS_ACCESS_APPROVAL_TOKEN_PREFIX = "clips-access-approval";
|
|
21
|
+
export const CLIPS_ACCESS_APPROVAL_TOKEN_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
22
|
+
export const CLIPS_ACCESS_APPROVAL_SESSION_KEY_PREFIX =
|
|
23
|
+
"clips-access-approval-token:";
|
|
24
|
+
|
|
19
25
|
/** Public share path for a recording, relative to the app base path. */
|
|
20
26
|
export function recordingSharePath(recordingId: string): string {
|
|
21
27
|
return `/share/${encodeURIComponent(recordingId)}`;
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
/** Owner-only route used by the one-click approval button in access emails. */
|
|
31
|
+
export function recordingAccessApprovalPath(
|
|
32
|
+
recordingId: string,
|
|
33
|
+
approvalToken: string,
|
|
34
|
+
): string {
|
|
35
|
+
const params = new URLSearchParams({
|
|
36
|
+
recordingId,
|
|
37
|
+
token: approvalToken,
|
|
38
|
+
});
|
|
39
|
+
return `/access-request/approve?${params.toString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Token-free continuation used after the approver signs in. */
|
|
43
|
+
export function recordingAccessApprovalContinuationPath(
|
|
44
|
+
recordingId: string,
|
|
45
|
+
): string {
|
|
46
|
+
const params = new URLSearchParams({ recordingId });
|
|
47
|
+
return `/access-request/approve?${params.toString()}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Tab-scoped key for carrying an approval token across the sign-in redirect. */
|
|
51
|
+
export function recordingAccessApprovalSessionKey(recordingId: string): string {
|
|
52
|
+
return `${CLIPS_ACCESS_APPROVAL_SESSION_KEY_PREFIX}${encodeURIComponent(recordingId)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export interface RecordingShareUrlParams {
|
|
25
56
|
recordingId: string;
|
|
26
57
|
/** Absolute origin, e.g. `https://clips.example.com` or a desktop serverUrl. */
|
|
@@ -51,6 +51,19 @@ export type ShareAttribution = {
|
|
|
51
51
|
via: string | undefined;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Keep only non-sensitive attribution state when a share page redirects to
|
|
56
|
+
* sign-in. Passwords and capability tokens must never enter auth URLs.
|
|
57
|
+
*/
|
|
58
|
+
export function buildShareContinuationQuery(
|
|
59
|
+
attribution: ShareAttribution,
|
|
60
|
+
): string {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
if (attribution.ref) params.set(REF_PARAM, attribution.ref);
|
|
63
|
+
if (attribution.via) params.set(VIA_PARAM, attribution.via);
|
|
64
|
+
return params.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
/**
|
|
55
68
|
* Read `ref`/`via` from a query string (e.g. `window.location.search`). Falls
|
|
56
69
|
* back to `ref=clip_share` so downstream attribution stays meaningful even when
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -184,6 +184,27 @@ import { ShareButton } from "@agent-native/core/client/sharing";
|
|
|
184
184
|
|
|
185
185
|
For list views, show `<VisibilityBadge visibility={row.visibility} />` next to each resource.
|
|
186
186
|
|
|
187
|
+
## Standard share surface
|
|
188
|
+
|
|
189
|
+
All app share popovers should use the same compact surface contract:
|
|
190
|
+
|
|
191
|
+
- Use the text-only `ShareTrigger` from `@agent-native/toolkit/sharing`.
|
|
192
|
+
- Render ordinary links with `ShareCopyRow`, which exposes a Copy action without
|
|
193
|
+
printing the raw URL.
|
|
194
|
+
- Keep general access and individual people access in the standard Core sharing
|
|
195
|
+
flow. The people flow supports email invites, roles, notifications, and
|
|
196
|
+
removal through the shared share actions.
|
|
197
|
+
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
|
+
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
|
+
content through the shared section shell.
|
|
200
|
+
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
|
+
action (for example, an embed-code editor), but they should retain the same
|
|
202
|
+
trigger, copy-row, access, and spacing language.
|
|
203
|
+
|
|
204
|
+
`ShareDisclosureSection` is the toolkit-owned shell for optional expandable
|
|
205
|
+
share details; use its `ShareAgentsSection` or `SharePeopleSection` wrappers
|
|
206
|
+
instead of creating another collapsible share panel in a template.
|
|
207
|
+
|
|
187
208
|
## Actions available everywhere
|
|
188
209
|
|
|
189
210
|
The framework auto-mounts these actions in every template — no per-template boilerplate:
|
|
@@ -696,6 +696,7 @@ export function useBlockFieldEditor({
|
|
|
696
696
|
content: string;
|
|
697
697
|
editorResetVersion: number;
|
|
698
698
|
onChange: (markdown: string) => void;
|
|
699
|
+
onSaveContent: (markdown: string) => Promise<boolean>;
|
|
699
700
|
} {
|
|
700
701
|
const key = `${documentId}:${propertyId}`;
|
|
701
702
|
|
|
@@ -876,7 +877,16 @@ export function useBlockFieldEditor({
|
|
|
876
877
|
controllerRef.current?.change(markdown);
|
|
877
878
|
}
|
|
878
879
|
|
|
879
|
-
|
|
880
|
+
async function onSaveContent(markdown: string) {
|
|
881
|
+
setContent(markdown);
|
|
882
|
+
const controller = controllerRef.current;
|
|
883
|
+
if (!controller) return false;
|
|
884
|
+
controller.change(markdown);
|
|
885
|
+
await controller.flush();
|
|
886
|
+
return controller.lastSaved === markdown;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return { content, editorResetVersion, onChange, onSaveContent };
|
|
880
890
|
}
|
|
881
891
|
|
|
882
892
|
/**
|
|
@@ -904,15 +914,16 @@ function AdditionalBlockEditor({
|
|
|
904
914
|
const propertyId = property.definition.id;
|
|
905
915
|
const initialContent =
|
|
906
916
|
typeof property.value === "string" ? property.value : "";
|
|
907
|
-
const { content, editorResetVersion, onChange } =
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
917
|
+
const { content, editorResetVersion, onChange, onSaveContent } =
|
|
918
|
+
useBlockFieldEditor({
|
|
919
|
+
documentId,
|
|
920
|
+
propertyId,
|
|
921
|
+
initialContent,
|
|
922
|
+
initialRevision: property.blocksField?.revision ?? 0,
|
|
923
|
+
save: setProperty.mutateAsync,
|
|
924
|
+
onRevisionConflict: () =>
|
|
925
|
+
toast.error(t("editor.blocksFieldRevisionConflict")),
|
|
926
|
+
});
|
|
916
927
|
|
|
917
928
|
return (
|
|
918
929
|
<VisualEditor
|
|
@@ -920,6 +931,7 @@ function AdditionalBlockEditor({
|
|
|
920
931
|
documentId={documentId}
|
|
921
932
|
content={content}
|
|
922
933
|
onChange={onChange}
|
|
934
|
+
onSaveContent={onSaveContent}
|
|
923
935
|
editable={canEdit}
|
|
924
936
|
localFileMode
|
|
925
937
|
/>
|
|
@@ -49,6 +49,7 @@ import { cn } from "@/lib/utils";
|
|
|
49
49
|
import { localContentComponents } from "@/local-components";
|
|
50
50
|
|
|
51
51
|
import { focusMostRecentEmptyToggleSummary } from "./extensions/NotionExtensions";
|
|
52
|
+
import { createImagePickerId } from "./image-upload";
|
|
52
53
|
import { buildLocalComponentSlashItems } from "./localComponentSlashItems";
|
|
53
54
|
import { MathRenderer } from "./MathRenderer";
|
|
54
55
|
import { buildRegistrySlashItems } from "./registrySlashItems";
|
|
@@ -179,7 +180,7 @@ export function insertMediaPlaceholder(
|
|
|
179
180
|
) {
|
|
180
181
|
const attrs =
|
|
181
182
|
type === "image"
|
|
182
|
-
? { src: null, alt: "" }
|
|
183
|
+
? { src: null, alt: "", uploadId: createImagePickerId() }
|
|
183
184
|
: type === "video"
|
|
184
185
|
? { src: null, sourcePanelOpen: true }
|
|
185
186
|
: { src: null };
|
|
@@ -95,9 +95,13 @@ import {
|
|
|
95
95
|
hasImageFiles,
|
|
96
96
|
hasVideoFiles,
|
|
97
97
|
audioUploadErrorMessage,
|
|
98
|
+
completeImageFileUpload,
|
|
99
|
+
createMediaUploadId,
|
|
100
|
+
ImageRenderError,
|
|
98
101
|
imageUploadErrorMessage,
|
|
99
102
|
uploadAudioFile,
|
|
100
103
|
uploadImageFile,
|
|
104
|
+
waitForRenderedImage,
|
|
101
105
|
uploadVideoFile,
|
|
102
106
|
videoUploadErrorMessage,
|
|
103
107
|
} from "./image-upload";
|
|
@@ -1020,6 +1024,7 @@ interface VisualEditorExtensionOptions {
|
|
|
1020
1024
|
avatarUrl?: string;
|
|
1021
1025
|
} | null;
|
|
1022
1026
|
onImageComment?: (quotedText: string, offsetTop: number) => void;
|
|
1027
|
+
onImageFilePickerRequest?: (request: PendingImagePicker) => void;
|
|
1023
1028
|
onJoinTitle?: (text: string) => void;
|
|
1024
1029
|
resolveNotionPageLink?: (notionPageId: string) => NotionPageLink | null;
|
|
1025
1030
|
onOpenNotionPageLink?: (documentId: string) => void;
|
|
@@ -1143,19 +1148,17 @@ function mediaNodeLabel(typeName: MediaNodeType) {
|
|
|
1143
1148
|
return "Audio";
|
|
1144
1149
|
}
|
|
1145
1150
|
|
|
1146
|
-
function createMediaUploadId(kind: MediaNodeType) {
|
|
1147
|
-
const random =
|
|
1148
|
-
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
1149
|
-
? crypto.randomUUID()
|
|
1150
|
-
: Math.random().toString(36).slice(2);
|
|
1151
|
-
return `${kind}-upload-${random}`;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
1151
|
interface PendingMediaUpload {
|
|
1155
1152
|
file: File;
|
|
1156
1153
|
uploadId: string;
|
|
1157
1154
|
}
|
|
1158
1155
|
|
|
1156
|
+
export interface PendingImagePicker {
|
|
1157
|
+
pickerId: string;
|
|
1158
|
+
position: number;
|
|
1159
|
+
attrs: Record<string, unknown>;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1159
1162
|
function insertPendingMediaNodes(
|
|
1160
1163
|
view: EditorView,
|
|
1161
1164
|
typeName: MediaNodeType,
|
|
@@ -1204,7 +1207,7 @@ function updatePendingMediaNode(
|
|
|
1204
1207
|
tr = tr.setNodeMarkup(pos, undefined, {
|
|
1205
1208
|
...node.attrs,
|
|
1206
1209
|
...attrs,
|
|
1207
|
-
uploadId: null,
|
|
1210
|
+
uploadId: attrs.uploadId ?? null,
|
|
1208
1211
|
});
|
|
1209
1212
|
found = true;
|
|
1210
1213
|
return false;
|
|
@@ -1218,6 +1221,130 @@ function updatePendingMediaNode(
|
|
|
1218
1221
|
return found;
|
|
1219
1222
|
}
|
|
1220
1223
|
|
|
1224
|
+
function replacePendingMediaUploadId(
|
|
1225
|
+
view: EditorView,
|
|
1226
|
+
typeName: MediaNodeType,
|
|
1227
|
+
currentUploadId: string,
|
|
1228
|
+
nextUploadId: string,
|
|
1229
|
+
) {
|
|
1230
|
+
let found = false;
|
|
1231
|
+
let tr = view.state.tr;
|
|
1232
|
+
|
|
1233
|
+
view.state.doc.descendants((node, pos) => {
|
|
1234
|
+
if (found) return false;
|
|
1235
|
+
if (
|
|
1236
|
+
node.type.name === typeName &&
|
|
1237
|
+
node.attrs.uploadId === currentUploadId
|
|
1238
|
+
) {
|
|
1239
|
+
tr = tr.setNodeMarkup(pos, undefined, {
|
|
1240
|
+
...node.attrs,
|
|
1241
|
+
uploadId: nextUploadId,
|
|
1242
|
+
});
|
|
1243
|
+
found = true;
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
return true;
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
if (found) view.dispatch(tr);
|
|
1250
|
+
return found;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function insertImageNodeAtPendingPosition(
|
|
1254
|
+
view: EditorView,
|
|
1255
|
+
request: PendingImagePicker,
|
|
1256
|
+
attrs: Record<string, unknown>,
|
|
1257
|
+
restoreSelection: boolean,
|
|
1258
|
+
) {
|
|
1259
|
+
const imageType = view.state.schema.nodes.image;
|
|
1260
|
+
if (!imageType) return false;
|
|
1261
|
+
const position = Math.min(
|
|
1262
|
+
Math.max(request.position, 0),
|
|
1263
|
+
view.state.doc.content.size,
|
|
1264
|
+
);
|
|
1265
|
+
try {
|
|
1266
|
+
let tr = view.state.tr.insert(
|
|
1267
|
+
position,
|
|
1268
|
+
imageType.create({ ...request.attrs, ...attrs }),
|
|
1269
|
+
);
|
|
1270
|
+
if (restoreSelection) {
|
|
1271
|
+
tr = tr.setSelection(NodeSelection.create(tr.doc, position));
|
|
1272
|
+
}
|
|
1273
|
+
view.dispatch(tr.scrollIntoView());
|
|
1274
|
+
if (restoreSelection) view.focus();
|
|
1275
|
+
return true;
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.error("Could not restore the pending image node:", error);
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
export function ensurePendingImageUpload(
|
|
1283
|
+
view: EditorView,
|
|
1284
|
+
request: PendingImagePicker,
|
|
1285
|
+
uploadId: string,
|
|
1286
|
+
) {
|
|
1287
|
+
if (replacePendingMediaUploadId(view, "image", request.pickerId, uploadId)) {
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
return insertImageNodeAtPendingPosition(view, request, { uploadId }, false);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
export function commitPendingImageUpload(
|
|
1294
|
+
view: EditorView,
|
|
1295
|
+
request: PendingImagePicker,
|
|
1296
|
+
uploadId: string,
|
|
1297
|
+
attrs: Record<string, unknown>,
|
|
1298
|
+
) {
|
|
1299
|
+
if (updatePendingMediaNode(view, "image", uploadId, attrs)) return true;
|
|
1300
|
+
return insertImageNodeAtPendingPosition(view, request, attrs, false);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function findPendingImageElement(view: EditorView, uploadId: string) {
|
|
1304
|
+
return (
|
|
1305
|
+
Array.from(view.dom.querySelectorAll<HTMLElement>("[data-image-upload-id]"))
|
|
1306
|
+
.find((element) => element.dataset.imageUploadId === uploadId)
|
|
1307
|
+
?.querySelector<HTMLImageElement>("img") ?? null
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
export function restorePendingImagePicker(
|
|
1312
|
+
view: EditorView,
|
|
1313
|
+
request: PendingImagePicker,
|
|
1314
|
+
currentUploadId = request.pickerId,
|
|
1315
|
+
) {
|
|
1316
|
+
let found = false;
|
|
1317
|
+
let nodePosition: number | null = null;
|
|
1318
|
+
let tr = view.state.tr;
|
|
1319
|
+
|
|
1320
|
+
view.state.doc.descendants((node, pos) => {
|
|
1321
|
+
if (found) return false;
|
|
1322
|
+
if (node.type.name === "image" && node.attrs.uploadId === currentUploadId) {
|
|
1323
|
+
tr = tr.setNodeMarkup(pos, undefined, {
|
|
1324
|
+
...request.attrs,
|
|
1325
|
+
uploadId: null,
|
|
1326
|
+
});
|
|
1327
|
+
nodePosition = pos;
|
|
1328
|
+
found = true;
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
return true;
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
if (found && nodePosition !== null) {
|
|
1335
|
+
tr = tr.setSelection(NodeSelection.create(tr.doc, nodePosition));
|
|
1336
|
+
view.dispatch(tr);
|
|
1337
|
+
view.focus();
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
return insertImageNodeAtPendingPosition(
|
|
1341
|
+
view,
|
|
1342
|
+
request,
|
|
1343
|
+
{ uploadId: null },
|
|
1344
|
+
true,
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1221
1348
|
function getVisualEditorPlaceholder({
|
|
1222
1349
|
editor,
|
|
1223
1350
|
node,
|
|
@@ -1499,6 +1626,7 @@ export function createVisualEditorExtensions({
|
|
|
1499
1626
|
localAwareness,
|
|
1500
1627
|
user,
|
|
1501
1628
|
onImageComment,
|
|
1629
|
+
onImageFilePickerRequest,
|
|
1502
1630
|
onJoinTitle,
|
|
1503
1631
|
resolveNotionPageLink,
|
|
1504
1632
|
onOpenNotionPageLink,
|
|
@@ -1558,6 +1686,7 @@ export function createVisualEditorExtensions({
|
|
|
1558
1686
|
HTMLAttributes: { class: "notion-image" },
|
|
1559
1687
|
documentId,
|
|
1560
1688
|
onImageComment,
|
|
1689
|
+
onImageFilePickerRequest,
|
|
1561
1690
|
}),
|
|
1562
1691
|
VideoNode.configure({
|
|
1563
1692
|
HTMLAttributes: { class: "notion-video" },
|
|
@@ -1880,6 +2009,8 @@ export function VisualEditor({
|
|
|
1880
2009
|
const t = useT();
|
|
1881
2010
|
const [isDraggingMedia, setIsDraggingMedia] = useState(false);
|
|
1882
2011
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
2012
|
+
const imageFileInputRef = useRef<HTMLInputElement>(null);
|
|
2013
|
+
const pendingImagePickerRef = useRef<PendingImagePicker | null>(null);
|
|
1883
2014
|
const onChangeRef = useRef(onChange);
|
|
1884
2015
|
onChangeRef.current = onChange;
|
|
1885
2016
|
const onSaveContentRef = useRef(onSaveContent);
|
|
@@ -1895,6 +2026,14 @@ export function VisualEditor({
|
|
|
1895
2026
|
},
|
|
1896
2027
|
[],
|
|
1897
2028
|
);
|
|
2029
|
+
const onImageFilePickerRequest = useCallback(
|
|
2030
|
+
(request: PendingImagePicker) => {
|
|
2031
|
+
if (pendingImagePickerRef.current) return;
|
|
2032
|
+
pendingImagePickerRef.current = request;
|
|
2033
|
+
imageFileInputRef.current?.click();
|
|
2034
|
+
},
|
|
2035
|
+
[],
|
|
2036
|
+
);
|
|
1898
2037
|
const resolveNotionPageLink = useCallback((notionPageId: string) => {
|
|
1899
2038
|
const normalized = notionPageId.replace(/-/g, "").toLowerCase();
|
|
1900
2039
|
return (
|
|
@@ -1946,6 +2085,7 @@ export function VisualEditor({
|
|
|
1946
2085
|
localAwareness,
|
|
1947
2086
|
user,
|
|
1948
2087
|
onImageComment: onComment,
|
|
2088
|
+
onImageFilePickerRequest,
|
|
1949
2089
|
onJoinTitle,
|
|
1950
2090
|
resolveNotionPageLink,
|
|
1951
2091
|
onOpenNotionPageLink,
|
|
@@ -1962,6 +2102,7 @@ export function VisualEditor({
|
|
|
1962
2102
|
user?.email,
|
|
1963
2103
|
user?.color,
|
|
1964
2104
|
onComment,
|
|
2105
|
+
onImageFilePickerRequest,
|
|
1965
2106
|
onJoinTitle,
|
|
1966
2107
|
resolveNotionPageLink,
|
|
1967
2108
|
onOpenNotionPageLink,
|
|
@@ -2014,6 +2155,7 @@ export function VisualEditor({
|
|
|
2014
2155
|
if (options?.immediate && onSaveContentRef.current) {
|
|
2015
2156
|
return onSaveContentRef.current(normalized);
|
|
2016
2157
|
}
|
|
2158
|
+
if (options?.immediate) return false;
|
|
2017
2159
|
// Don't persist an empty doc before Collaboration has seeded (would
|
|
2018
2160
|
// clobber DB content with an empty string). `registerEmitted` records
|
|
2019
2161
|
// this as the last-emitted value and returns false to skip the save.
|
|
@@ -2225,6 +2367,93 @@ export function VisualEditor({
|
|
|
2225
2367
|
},
|
|
2226
2368
|
});
|
|
2227
2369
|
|
|
2370
|
+
const handleImageFileInputChange = useCallback(
|
|
2371
|
+
async (event: Event) => {
|
|
2372
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
2373
|
+
const file = input.files?.[0];
|
|
2374
|
+
input.value = "";
|
|
2375
|
+
const request = pendingImagePickerRef.current;
|
|
2376
|
+
pendingImagePickerRef.current = null;
|
|
2377
|
+
if (!editor || !file || !request) return;
|
|
2378
|
+
|
|
2379
|
+
const uploadId = createMediaUploadId("image");
|
|
2380
|
+
if (!ensurePendingImageUpload(editor.view, request, uploadId)) return;
|
|
2381
|
+
|
|
2382
|
+
const toastId = toast.loading(t("editor.media.uploadingImage"));
|
|
2383
|
+
let staged = false;
|
|
2384
|
+
let committed = false;
|
|
2385
|
+
try {
|
|
2386
|
+
await completeImageFileUpload({
|
|
2387
|
+
file,
|
|
2388
|
+
stageAttributes: (src) => {
|
|
2389
|
+
if (editor.isDestroyed) return;
|
|
2390
|
+
staged = commitPendingImageUpload(editor.view, request, uploadId, {
|
|
2391
|
+
src,
|
|
2392
|
+
uploadId,
|
|
2393
|
+
});
|
|
2394
|
+
},
|
|
2395
|
+
waitForRender: async () => {
|
|
2396
|
+
if (!staged) throw new Error(t("empty.genericError"));
|
|
2397
|
+
await waitForRenderedImage(() =>
|
|
2398
|
+
findPendingImageElement(editor.view, uploadId),
|
|
2399
|
+
);
|
|
2400
|
+
},
|
|
2401
|
+
commitAttributes: (src) => {
|
|
2402
|
+
if (editor.isDestroyed) return;
|
|
2403
|
+
committed = commitPendingImageUpload(
|
|
2404
|
+
editor.view,
|
|
2405
|
+
request,
|
|
2406
|
+
uploadId,
|
|
2407
|
+
{ src, uploadId: null },
|
|
2408
|
+
);
|
|
2409
|
+
},
|
|
2410
|
+
persistCommittedImage: async () => {
|
|
2411
|
+
if (!committed || editor.isDestroyed) return false;
|
|
2412
|
+
return await persistEditorContent(editor, {
|
|
2413
|
+
immediate: true,
|
|
2414
|
+
userInitiated: true,
|
|
2415
|
+
});
|
|
2416
|
+
},
|
|
2417
|
+
});
|
|
2418
|
+
if (!committed) throw new Error(t("empty.genericError"));
|
|
2419
|
+
toast.success(t("editor.media.imageAdded"), { id: toastId });
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
if (editor.isDestroyed) {
|
|
2422
|
+
toast.dismiss(toastId);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
if (!committed) {
|
|
2426
|
+
restorePendingImagePicker(editor.view, request, uploadId);
|
|
2427
|
+
}
|
|
2428
|
+
toast.error(
|
|
2429
|
+
error instanceof ImageRenderError
|
|
2430
|
+
? t("editor.media.imageBroken")
|
|
2431
|
+
: imageUploadErrorMessage(error),
|
|
2432
|
+
{ id: toastId },
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
},
|
|
2436
|
+
[editor, t],
|
|
2437
|
+
);
|
|
2438
|
+
|
|
2439
|
+
const handleImageFileInputCancel = useCallback(() => {
|
|
2440
|
+
const request = pendingImagePickerRef.current;
|
|
2441
|
+
pendingImagePickerRef.current = null;
|
|
2442
|
+
if (!editor || !request) return;
|
|
2443
|
+
restorePendingImagePicker(editor.view, request);
|
|
2444
|
+
}, [editor]);
|
|
2445
|
+
|
|
2446
|
+
useEffect(() => {
|
|
2447
|
+
const input = imageFileInputRef.current;
|
|
2448
|
+
if (!input) return;
|
|
2449
|
+
input.addEventListener("change", handleImageFileInputChange);
|
|
2450
|
+
input.addEventListener("cancel", handleImageFileInputCancel);
|
|
2451
|
+
return () => {
|
|
2452
|
+
input.removeEventListener("change", handleImageFileInputChange);
|
|
2453
|
+
input.removeEventListener("cancel", handleImageFileInputCancel);
|
|
2454
|
+
};
|
|
2455
|
+
}, [editor, handleImageFileInputCancel, handleImageFileInputChange]);
|
|
2456
|
+
|
|
2228
2457
|
// The shared seed / reconcile / lead-client / onUpdate-guard logic, with
|
|
2229
2458
|
// Content's NFM serializer injected so the editor reads/writes the exact same
|
|
2230
2459
|
// bytes as before (docToNfm / nfmToDoc / canonicalizeNfm, and the
|
|
@@ -2563,6 +2792,13 @@ export function VisualEditor({
|
|
|
2563
2792
|
<RegistryBlockDataProvider value={registryBlockDataValue}>
|
|
2564
2793
|
<EditorContent editor={editor} />
|
|
2565
2794
|
</RegistryBlockDataProvider>
|
|
2795
|
+
<input
|
|
2796
|
+
ref={imageFileInputRef}
|
|
2797
|
+
type="file"
|
|
2798
|
+
className="hidden"
|
|
2799
|
+
tabIndex={-1}
|
|
2800
|
+
aria-hidden="true"
|
|
2801
|
+
/>
|
|
2566
2802
|
</div>
|
|
2567
2803
|
);
|
|
2568
2804
|
}
|