@agent-native/core 0.141.6 → 0.144.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/agent-native.eject.json +9 -0
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +3 -14
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +11 -6
- package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +5 -5
- package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/AGENTS.md +37 -40
- package/corpus/templates/analytics/actions/compose-dashboard.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +135 -0
- package/corpus/templates/analytics/actions/navigate.ts +5 -6
- package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +46 -13
- package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +0 -1
- package/corpus/templates/analytics/actions/view-screen.ts +0 -20
- package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
- package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +5 -5
- package/corpus/templates/analytics/app/i18n-data.ts +682 -90
- package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
- package/corpus/templates/analytics/app/pages/Agents.tsx +0 -3
- package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +22 -43
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +23 -20
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/analytics/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/analytics/app/routes/team.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-settings-no-longer-includes-an-unnecessary-about-section.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +1 -1
- package/corpus/templates/analytics/netlify.toml +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
- package/corpus/templates/analytics/scripts/first-party-analytics-bigquery.sql +104 -0
- package/corpus/templates/analytics/server/db/index.ts +27 -0
- package/corpus/templates/analytics/server/db/schema.ts +10 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -2
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +1147 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +34 -15
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -4
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -4
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
- package/corpus/templates/analytics/server/plugins/db.ts +52 -18
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +36 -8
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +16 -1
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/assets/AGENTS.md +1 -0
- package/corpus/templates/assets/actions/_helpers.ts +12 -0
- package/corpus/templates/assets/actions/generate-image.ts +9 -3
- package/corpus/templates/assets/actions/import-style-from-url.ts +115 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
- package/corpus/templates/assets/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/assets/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/assets/app/routes/settings.tsx +5 -0
- package/corpus/templates/assets/app/routes/team.tsx +1 -1
- package/corpus/templates/assets/changelog/2026-08-05-brand-kits-can-import-a-website-s-hydrated-visual-language-a.md +6 -0
- package/corpus/templates/assets/changelog/2026-08-05-generated-images-now-hand-off-embeddable-previews-to-other-w.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +58 -0
- package/corpus/templates/assets/shared/api.ts +12 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
- package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
- package/corpus/templates/brain/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/brain/app/routes/sources.tsx +15 -7
- package/corpus/templates/brain/app/routes/team.tsx +1 -1
- package/corpus/templates/brain/app/routes.ts +1 -1
- package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/AGENTS.md +11 -32
- package/corpus/templates/calendar/actions/update-event.ts +107 -1
- package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/calendar/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/chat/app/root.tsx +1 -1
- package/corpus/templates/chat/app/routes/agent.tsx +7 -7
- package/corpus/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/chat/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/chat/app/routes/team.tsx +1 -1
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
- package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
- package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
- package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +48 -20
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
- package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
- package/corpus/templates/clips/server/plugins/db.ts +214 -26
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/content/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/content/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/content/e2e/global-setup.ts +1 -1
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
- package/corpus/templates/crm/server/plugins/db.ts +11 -8
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -1
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/design/actions/analyze-brand-assets.ts +7 -7
- package/corpus/templates/design/actions/import-from-url.ts +6 -5
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +2 -2
- package/corpus/templates/design/app/pages/Index.tsx +6 -2
- package/corpus/templates/design/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/design/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/design/app/routes/team.tsx +1 -1
- package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
- package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
- package/corpus/templates/design/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-de.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +1 -1
- package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/dispatch/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
- package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/factory/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/factory/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/factory/app/routes/team.tsx +1 -1
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/forms/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/forms/shared/navigation.ts +1 -1
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/macros/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/macros/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/macros/app/routes/team.tsx +1 -1
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/mail/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/mail/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/plan/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/plan/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/plan/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +40 -2
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/slides/AGENTS.md +7 -9
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/actions/analyze-brand-assets.ts +7 -7
- package/corpus/templates/slides/actions/export-pptx.ts +402 -36
- package/corpus/templates/slides/actions/import-file.ts +46 -85
- package/corpus/templates/slides/actions/import-from-url.ts +6 -6
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
- package/corpus/templates/slides/actions/import-pptx.ts +85 -60
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +176 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +13 -3
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +71 -20
- package/corpus/templates/slides/app/global.css +6 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +10 -6
- package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
- package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -0
- package/corpus/templates/slides/app/pages/Index.tsx +107 -6
- package/corpus/templates/slides/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/slides/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/slides/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-08-05-failed-deck-creation-and-reference-uploads-now-recover-clean.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-slides-recover-completed-generation-results-after-a-sync-hic.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-st.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +236 -13
- package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
- package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
- package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
- package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -3
- package/corpus/templates/slides/server/lib/emails.ts +7 -2
- package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
- package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -3
- package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
- package/corpus/templates/slides/shared/upload-types.ts +3 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/tasks/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/tasks/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/tasks/app/routes/settings.$.tsx +1 -0
- package/dist/agent/engine/ai-sdk-engine.js +49 -3
- package/dist/agent/production-agent.d.ts +18 -0
- package/dist/agent/production-agent.js +80 -1
- package/dist/agent/types.d.ts +7 -0
- package/dist/cli/agent-plugin.d.ts +112 -0
- package/dist/cli/agent-plugin.js +1081 -0
- package/dist/cli/app-skill.js +39 -1
- package/dist/cli/code-agent-executor.d.ts +3 -0
- package/dist/cli/code-agent-executor.js +51 -13
- package/dist/cli/index.js +13 -0
- package/dist/cli/templates-meta.js +1 -1
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +9 -2
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +23 -8
- package/dist/client/NewWorkspaceAppFlow.js +1 -0
- package/dist/client/agent-chat-adapter.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +153 -7
- package/dist/client/agent-page/AgentWorkspaceContent.d.ts +1 -1
- package/dist/client/agent-page/AgentWorkspaceContent.js +13 -13
- package/dist/client/assistant-ui-recovery.d.ts +2 -1
- package/dist/client/assistant-ui-recovery.js +7 -3
- package/dist/client/chat/run-recovery.js +5 -1
- package/dist/client/embed-auth.js +6 -2
- package/dist/client/error-format.js +12 -0
- package/dist/client/integrations/IntegrationsPanel.js +40 -6
- package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
- package/dist/client/org/OrgSwitcher.js +2 -2
- package/dist/client/org/TeamPage.js +92 -72
- package/dist/client/require-session.d.ts +1 -1
- package/dist/client/require-session.js +4 -4
- package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
- package/dist/client/resources/McpConnectionSuggestion.js +27 -10
- package/dist/client/resources/McpIntegrationDialog.js +77 -45
- package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
- package/dist/client/resources/mcp-integration-catalog.js +255 -2
- package/dist/client/resources/mcp-integration-logos.js +18 -2
- package/dist/client/resources/use-mcp-servers.d.ts +5 -0
- package/dist/client/resources/use-mcp-servers.js +36 -0
- package/dist/client/settings/AccountSettingsCard.js +42 -36
- package/dist/client/settings/AutomationsSection.js +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -1
- package/dist/client/settings/DemoModeSection.js +4 -1
- package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
- package/dist/client/settings/SchedulingTimezoneField.d.ts +3 -1
- package/dist/client/settings/SchedulingTimezoneField.js +9 -5
- package/dist/client/settings/SettingsPanel.d.ts +4 -1
- package/dist/client/settings/SettingsPanel.js +21 -21
- package/dist/client/settings/SettingsTabsPage.d.ts +2 -2
- package/dist/client/settings/SettingsTabsPage.js +98 -45
- package/dist/client/settings/VoiceTranscriptionSection.js +6 -1
- package/dist/client/settings/useSettingsPanelController.js +47 -8
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +9 -0
- package/dist/db/client.js +51 -16
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/ddl-guard.d.ts +11 -10
- package/dist/db/ddl-guard.js +40 -12
- package/dist/deploy/build.d.ts +2 -0
- package/dist/deploy/build.js +97 -4
- package/dist/deploy/workspace-deploy.js +0 -2
- package/dist/guards/no-env-credentials.js +9 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/pptx.d.ts +68 -0
- package/dist/ingestion/pptx.js +660 -213
- package/dist/ingestion/website.d.ts +30 -0
- package/dist/integrations/plugin.js +9 -6
- package/dist/localization/default-messages.d.ts +59 -0
- package/dist/localization/default-messages.js +62 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp-client/oauth-routes.d.ts +2 -0
- package/dist/mcp-client/oauth-routes.js +74 -7
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.js +49 -0
- package/dist/navigation/index.js +16 -5
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +11 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/store.js +15 -1
- package/dist/scripts/call-agent.js +45 -4
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +36 -1
- package/dist/server/agent-chat-plugin.js +29 -8
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +55 -17
- package/dist/server/better-auth-instance.js +5 -3
- package/dist/server/core-routes-plugin.d.ts +8 -1
- package/dist/server/core-routes-plugin.js +28 -3
- package/dist/server/credential-provider.d.ts +3 -3
- package/dist/server/credential-provider.js +90 -4
- package/dist/server/identity-sso.js +2 -1
- package/dist/server/onboarding-html.d.ts +2 -0
- package/dist/server/onboarding-html.js +171 -62
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/self-dispatch.d.ts +5 -4
- package/dist/server/self-dispatch.js +25 -14
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.js +1 -1
- package/dist/server/workspace-provider-oauth.js +1 -1
- package/dist/setup-connections/onboarding.js +1 -1
- package/dist/shared/cache-control.d.ts +26 -2
- package/dist/shared/cache-control.js +43 -2
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/password-policy.d.ts +3 -0
- package/dist/shared/password-policy.js +3 -0
- package/dist/shared/sign-in-journey.d.ts +5 -2
- package/dist/shared/sign-in-journey.js +10 -4
- package/dist/sharing/actions/set-resource-visibility.js +16 -4
- package/dist/sharing/registry.d.ts +14 -0
- package/dist/styles/agent-native.css +6 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/dist/templates/chat/app/root.tsx +1 -1
- package/dist/templates/chat/app/routes/agent.tsx +7 -7
- package/dist/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/dist/templates/chat/app/routes/settings.$.tsx +1 -0
- package/dist/templates/chat/app/routes/team.tsx +1 -1
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-root/AGENTS.md +7 -0
- package/dist/user-profile/actions/change-password.js +8 -3
- package/dist/user-profile/actions/set-password.js +5 -1
- package/dist/vite/action-types-plugin.js +59 -0
- package/docs/content/authentication.mdx +4 -5
- package/docs/content/skills-guide.mdx +13 -0
- package/docs/content/template-analytics-developers.mdx +1 -1
- package/docs/content/template-calendar-developers.mdx +1 -1
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/template-mail-developers.mdx +1 -1
- package/docs/content/template-plan-developers.mdx +1 -1
- package/docs/content/toolkit-command-navigation.mdx +6 -6
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/toolkit-settings.mdx +8 -8
- package/docs/content/toolkit-setup-connections.mdx +1 -1
- package/package.json +6 -2
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/src/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/src/templates/chat/app/root.tsx +1 -1
- package/src/templates/chat/app/routes/agent.tsx +7 -7
- package/src/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/src/templates/chat/app/routes/settings.$.tsx +1 -0
- package/src/templates/chat/app/routes/team.tsx +1 -1
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-root/AGENTS.md +7 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +0 -62
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +0 -369
- package/corpus/templates/analytics/actions/list-dashboard-templates.ts +0 -47
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +0 -34
- package/corpus/templates/analytics/app/routes/catalog.tsx +0 -240
- package/src/templates/default/actions/navigate.spec.ts +0 -53
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
createSlidesCanvasGestureController,
|
|
72
72
|
isWithinSlidesCanvasEdgeMoveBand,
|
|
73
73
|
resolveSlidesCanvasPointerIntent,
|
|
74
|
+
resolveSlidesCanvasDragTarget,
|
|
74
75
|
slidesCanvasInteractionCore,
|
|
75
76
|
} from "./canvas-interactions";
|
|
76
77
|
import ImageOverlay from "./ImageOverlay";
|
|
@@ -125,6 +126,12 @@ import {
|
|
|
125
126
|
} from "./slide-object-interactions";
|
|
126
127
|
import { getPassiveSlidePresenceUsers } from "./slide-presence";
|
|
127
128
|
import { type SlideStylePatch, type SlideStyleSnapshot } from "./slide-style";
|
|
129
|
+
import {
|
|
130
|
+
findSmartBlock,
|
|
131
|
+
isInlineTextElement,
|
|
132
|
+
isTextLeaf,
|
|
133
|
+
shouldStampBuilderId,
|
|
134
|
+
} from "./slide-text-targets";
|
|
128
135
|
import { SlideContextToolbar } from "./SlideContextToolbar";
|
|
129
136
|
import { SlideOverflowWarning } from "./SlideOverflowWarning";
|
|
130
137
|
import { SpeakerNotesPanel } from "./SpeakerNotesPanel";
|
|
@@ -145,6 +152,15 @@ function isPersistedFreeformObject(element: HTMLElement): boolean {
|
|
|
145
152
|
);
|
|
146
153
|
}
|
|
147
154
|
|
|
155
|
+
function isSlideCanvasShell(element: HTMLElement): boolean {
|
|
156
|
+
return (
|
|
157
|
+
element.classList.contains("fmd-slide") ||
|
|
158
|
+
element.classList.contains("fmd-autofit-scale") ||
|
|
159
|
+
element.hasAttribute("data-fmd-autofit-content") ||
|
|
160
|
+
element.hasAttribute("data-slide-canvas")
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
148
164
|
function ensureBuilderId(element: HTMLElement): string {
|
|
149
165
|
const existing = element.getAttribute("data-builder-id");
|
|
150
166
|
if (existing) return existing;
|
|
@@ -153,12 +169,16 @@ function ensureBuilderId(element: HTMLElement): string {
|
|
|
153
169
|
return id;
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
/** Stamp
|
|
172
|
+
/** Stamp selectable elements inside a container with transient builder ids. */
|
|
157
173
|
function stampBuilderIds(container: HTMLElement) {
|
|
158
174
|
const elements = container.querySelectorAll("*");
|
|
159
175
|
elements.forEach((el) => {
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
const element = el as HTMLElement;
|
|
177
|
+
if (!shouldStampBuilderId(element)) {
|
|
178
|
+
element.removeAttribute("data-builder-id");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
ensureBuilderId(element);
|
|
162
182
|
});
|
|
163
183
|
}
|
|
164
184
|
|
|
@@ -169,108 +189,9 @@ function getBuilderSelector(el: HTMLElement): string | null {
|
|
|
169
189
|
return null;
|
|
170
190
|
}
|
|
171
191
|
|
|
172
|
-
/** Inline tags allowed inside a "text leaf" element */
|
|
173
|
-
const INLINE_TAGS = new Set([
|
|
174
|
-
"SPAN",
|
|
175
|
-
"STRONG",
|
|
176
|
-
"EM",
|
|
177
|
-
"B",
|
|
178
|
-
"I",
|
|
179
|
-
"U",
|
|
180
|
-
"A",
|
|
181
|
-
"BR",
|
|
182
|
-
"CODE",
|
|
183
|
-
"SUB",
|
|
184
|
-
"SUP",
|
|
185
|
-
"MARK",
|
|
186
|
-
"SMALL",
|
|
187
|
-
"S",
|
|
188
|
-
"FONT",
|
|
189
|
-
]);
|
|
190
|
-
|
|
191
192
|
/** Block tags that can hold rich multi-paragraph content */
|
|
192
193
|
const RICH_BLOCK_TAGS = new Set(["P", "DIV", "BLOCKQUOTE", "LI", "UL", "OL"]);
|
|
193
194
|
|
|
194
|
-
/**
|
|
195
|
-
* A "text leaf" is a block-level element whose children are only text nodes
|
|
196
|
-
* or inline elements — i.e. it's safe to make contentEditable without
|
|
197
|
-
* exposing layout containers to editing.
|
|
198
|
-
*/
|
|
199
|
-
function isTextLeaf(el: HTMLElement): boolean {
|
|
200
|
-
if (!el || el.tagName === "IMG") return false;
|
|
201
|
-
if (el.classList.contains("fmd-img-placeholder")) return false;
|
|
202
|
-
// A user-placed text box stays editable even after its content is fully
|
|
203
|
-
// deleted, so an emptied box does not degrade into an unrecognized shape.
|
|
204
|
-
if (el.classList.contains("fmd-text-box")) return true;
|
|
205
|
-
// Must contain some text
|
|
206
|
-
if (!el.textContent?.trim()) return false;
|
|
207
|
-
for (const child of Array.from(el.children)) {
|
|
208
|
-
if (!INLINE_TAGS.has(child.tagName)) return false;
|
|
209
|
-
}
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* A "smart group" is a container whose children are all text leaves OR
|
|
215
|
-
* nested smart groups — i.e. a container that exists purely to hold text
|
|
216
|
-
* chunks with no images / layout islands mixed in. These are safe to edit
|
|
217
|
-
* as a single contentEditable region so users can work with multiple
|
|
218
|
-
* chunks (bullet rows, stat pairs, bodies of paragraphs) at once.
|
|
219
|
-
*/
|
|
220
|
-
function isSmartGroup(el: HTMLElement): boolean {
|
|
221
|
-
if (!el) return false;
|
|
222
|
-
if (el.tagName === "IMG") return false;
|
|
223
|
-
if (el.classList.contains("fmd-img-placeholder")) return false;
|
|
224
|
-
const children = Array.from(el.children);
|
|
225
|
-
if (children.length < 2) return false;
|
|
226
|
-
// Must contain some text overall
|
|
227
|
-
if (!el.textContent?.trim()) return false;
|
|
228
|
-
for (const child of children) {
|
|
229
|
-
const c = child as HTMLElement;
|
|
230
|
-
if (c.tagName === "IMG") return false;
|
|
231
|
-
if (c.classList.contains("fmd-img-placeholder")) return false;
|
|
232
|
-
if (!isTextLeaf(c) && !isSmartGroup(c)) return false;
|
|
233
|
-
}
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Find the "smart block" to edit for a given click target. A smart block is
|
|
239
|
-
* either:
|
|
240
|
-
* - a text leaf (single line / single rich text block), or
|
|
241
|
-
* - a smart group that is itself inside the top-level fmd-slide wrapper —
|
|
242
|
-
* i.e. a logical grouping of text chunks (a bullet list, a pair of
|
|
243
|
-
* stat number + label, etc.).
|
|
244
|
-
*
|
|
245
|
-
* We walk up from the click target and prefer the DEEPEST meaningful block
|
|
246
|
-
* so each double-click targets the most specific editable region. Users who
|
|
247
|
-
* want to edit multiple chunks together can double-click the whitespace
|
|
248
|
-
* between them, or double-click a group's border — the click will resolve
|
|
249
|
-
* to the group element rather than any single child.
|
|
250
|
-
*/
|
|
251
|
-
function findSmartBlock(
|
|
252
|
-
target: HTMLElement,
|
|
253
|
-
root: HTMLElement,
|
|
254
|
-
): HTMLElement | null {
|
|
255
|
-
let el: HTMLElement | null = target;
|
|
256
|
-
while (el && root.contains(el)) {
|
|
257
|
-
if (isTextLeaf(el)) {
|
|
258
|
-
// A list item (native <li> or a styled bullet row) must be edited
|
|
259
|
-
// together with its siblings so Enter can add a new item — editing a
|
|
260
|
-
// single item in isolation traps Enter inside that one row.
|
|
261
|
-
const list = findEnclosingList(el, root);
|
|
262
|
-
if (list) return list;
|
|
263
|
-
return el;
|
|
264
|
-
}
|
|
265
|
-
// The click landed on a container (e.g. a flex wrapper around stat
|
|
266
|
-
// rows). If that container is a smart group, use IT as the block so
|
|
267
|
-
// the user gets multi-chunk editing of everything inside.
|
|
268
|
-
if (isSmartGroup(el)) return el;
|
|
269
|
-
el = el.parentElement;
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
195
|
/** Strip renderer/editor-only attributes from an HTML string before saving */
|
|
275
196
|
function stripBuilderIds(html: string): string {
|
|
276
197
|
let cleaned = html;
|
|
@@ -2485,16 +2406,19 @@ export default function SlideEditor({
|
|
|
2485
2406
|
]);
|
|
2486
2407
|
|
|
2487
2408
|
/**
|
|
2488
|
-
* Find the nearest meaningful
|
|
2489
|
-
*
|
|
2490
|
-
*
|
|
2491
|
-
* "deselect / start marquee", not "select the whole slide").
|
|
2409
|
+
* Find the nearest meaningful element for multi-select from a click target.
|
|
2410
|
+
* Inline text markup is presentation only, so walk through it to the
|
|
2411
|
+
* containing block. The slide-content root itself starts a marquee instead.
|
|
2492
2412
|
*/
|
|
2493
2413
|
const findSelectableId = useCallback(
|
|
2494
2414
|
(target: HTMLElement, slideContent: HTMLElement): string | null => {
|
|
2495
2415
|
let el: HTMLElement | null = target;
|
|
2496
2416
|
while (el && slideContent.contains(el) && el !== slideContent) {
|
|
2497
2417
|
if (el.classList.contains("fmd-layout-spacer")) return null;
|
|
2418
|
+
if (isInlineTextElement(el)) {
|
|
2419
|
+
el = el.parentElement;
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2498
2422
|
const id = el.getAttribute("data-builder-id");
|
|
2499
2423
|
if (id) return id;
|
|
2500
2424
|
el = el.parentElement;
|
|
@@ -2509,6 +2433,10 @@ export default function SlideEditor({
|
|
|
2509
2433
|
let el: HTMLElement | null = target;
|
|
2510
2434
|
while (el && slideContent.contains(el) && el !== slideContent) {
|
|
2511
2435
|
if (el.classList.contains("fmd-layout-spacer")) return null;
|
|
2436
|
+
if (isInlineTextElement(el)) {
|
|
2437
|
+
el = el.parentElement;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2512
2440
|
if (el.getAttribute("data-builder-id")) return el;
|
|
2513
2441
|
el = el.parentElement;
|
|
2514
2442
|
}
|
|
@@ -2522,9 +2450,10 @@ export default function SlideEditor({
|
|
|
2522
2450
|
(target: HTMLElement, slideContent: HTMLElement): boolean => {
|
|
2523
2451
|
// The slide root itself, or a direct child container that has no text /
|
|
2524
2452
|
// image content at the point of click. Simplest heuristic: target IS
|
|
2525
|
-
// the slide-content element, OR it's the
|
|
2526
|
-
|
|
2527
|
-
|
|
2453
|
+
// the slide-content element, OR it's one of the renderer's structural
|
|
2454
|
+
// shells. Those shells are not user objects and must remain marquee
|
|
2455
|
+
// surfaces rather than becoming draggable freeform objects.
|
|
2456
|
+
if (target === slideContent || isSlideCanvasShell(target)) return true;
|
|
2528
2457
|
return false;
|
|
2529
2458
|
},
|
|
2530
2459
|
[],
|
|
@@ -2840,9 +2769,7 @@ export default function SlideEditor({
|
|
|
2840
2769
|
const slideCanvas = element.closest(
|
|
2841
2770
|
".fmd-slide, [data-slide-canvas]",
|
|
2842
2771
|
) as HTMLElement | null;
|
|
2843
|
-
if (!slideCanvas
|
|
2844
|
-
return;
|
|
2845
|
-
}
|
|
2772
|
+
if (!slideCanvas) return;
|
|
2846
2773
|
|
|
2847
2774
|
const slideRect = slideCanvas.getBoundingClientRect();
|
|
2848
2775
|
const slideWidth = slideCanvas.offsetWidth;
|
|
@@ -2866,10 +2793,74 @@ export default function SlideEditor({
|
|
|
2866
2793
|
suppressNextClickRef.current = true;
|
|
2867
2794
|
}
|
|
2868
2795
|
|
|
2869
|
-
const
|
|
2796
|
+
const initiallyAbsolute =
|
|
2797
|
+
getComputedStyle(element).position === "absolute";
|
|
2798
|
+
let origin = initiallyAbsolute ? getObjectGeometry(element) : null;
|
|
2870
2799
|
const originalObjectId = element.getAttribute("data-slide-object-id");
|
|
2800
|
+
const originalClassName = element.className;
|
|
2801
|
+
const originalStyle = element.getAttribute("style");
|
|
2802
|
+
const originalContentEditable = element.getAttribute("contenteditable");
|
|
2803
|
+
const originalEditingBlock = element.getAttribute("data-editing-block");
|
|
2871
2804
|
let activeElement = element;
|
|
2872
2805
|
let clone: HTMLElement | null = null;
|
|
2806
|
+
let restoreMarkdownTree: (() => void) | undefined;
|
|
2807
|
+
let promotedToFreeform = false;
|
|
2808
|
+
|
|
2809
|
+
const promoteForDrag = () => {
|
|
2810
|
+
if (origin) return true;
|
|
2811
|
+
const frozen = freezeElementForFreeformSelection(element);
|
|
2812
|
+
if (!frozen) {
|
|
2813
|
+
return false;
|
|
2814
|
+
}
|
|
2815
|
+
restoreMarkdownTree = frozen.restoreMarkdownTree;
|
|
2816
|
+
if (getComputedStyle(element).position !== "absolute") {
|
|
2817
|
+
restoreMarkdownTree?.();
|
|
2818
|
+
restoreMarkdownTree = undefined;
|
|
2819
|
+
return false;
|
|
2820
|
+
}
|
|
2821
|
+
promotedToFreeform = true;
|
|
2822
|
+
origin = getObjectGeometry(element);
|
|
2823
|
+
return true;
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
const removeFreeformLayoutSpacer = () => {
|
|
2827
|
+
const objectId = element.getAttribute("data-slide-object-id");
|
|
2828
|
+
if (!objectId) return;
|
|
2829
|
+
const spacer = Array.from(
|
|
2830
|
+
element.parentElement?.querySelectorAll<HTMLElement>(
|
|
2831
|
+
"[data-slide-layout-spacer-for]",
|
|
2832
|
+
) ?? [],
|
|
2833
|
+
).find(
|
|
2834
|
+
(candidate) =>
|
|
2835
|
+
candidate.getAttribute("data-slide-layout-spacer-for") === objectId,
|
|
2836
|
+
);
|
|
2837
|
+
spacer?.remove();
|
|
2838
|
+
};
|
|
2839
|
+
|
|
2840
|
+
const restorePromotedElement = () => {
|
|
2841
|
+
removeFreeformLayoutSpacer();
|
|
2842
|
+
restoreMarkdownTree?.();
|
|
2843
|
+
if (!restoreMarkdownTree) {
|
|
2844
|
+
element.className = originalClassName;
|
|
2845
|
+
if (originalStyle === null) element.removeAttribute("style");
|
|
2846
|
+
else element.setAttribute("style", originalStyle);
|
|
2847
|
+
if (originalContentEditable === null) {
|
|
2848
|
+
element.removeAttribute("contenteditable");
|
|
2849
|
+
} else {
|
|
2850
|
+
element.setAttribute("contenteditable", originalContentEditable);
|
|
2851
|
+
}
|
|
2852
|
+
if (originalEditingBlock === null) {
|
|
2853
|
+
element.removeAttribute("data-editing-block");
|
|
2854
|
+
} else {
|
|
2855
|
+
element.setAttribute("data-editing-block", originalEditingBlock);
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (originalObjectId) {
|
|
2859
|
+
element.setAttribute("data-slide-object-id", originalObjectId);
|
|
2860
|
+
} else {
|
|
2861
|
+
element.removeAttribute("data-slide-object-id");
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2873
2864
|
|
|
2874
2865
|
const stop = () => {
|
|
2875
2866
|
window.removeEventListener("pointermove", onMove);
|
|
@@ -2883,13 +2874,16 @@ export default function SlideEditor({
|
|
|
2883
2874
|
const restore = () => {
|
|
2884
2875
|
if (clone) {
|
|
2885
2876
|
clone.remove();
|
|
2886
|
-
} else {
|
|
2887
|
-
applyObjectGeometry(element, origin);
|
|
2888
2877
|
}
|
|
2889
|
-
if (
|
|
2890
|
-
|
|
2878
|
+
if (promotedToFreeform) {
|
|
2879
|
+
restorePromotedElement();
|
|
2891
2880
|
} else {
|
|
2892
|
-
|
|
2881
|
+
if (!clone && origin) applyObjectGeometry(element, origin);
|
|
2882
|
+
if (originalObjectId) {
|
|
2883
|
+
element.setAttribute("data-slide-object-id", originalObjectId);
|
|
2884
|
+
} else {
|
|
2885
|
+
element.removeAttribute("data-slide-object-id");
|
|
2886
|
+
}
|
|
2893
2887
|
}
|
|
2894
2888
|
const selector = getBuilderSelector(element);
|
|
2895
2889
|
if (selector) selectElementForStyling(element, selector);
|
|
@@ -2897,6 +2891,13 @@ export default function SlideEditor({
|
|
|
2897
2891
|
|
|
2898
2892
|
const controller = createSlidesCanvasGestureController({
|
|
2899
2893
|
preview: (gesture) => {
|
|
2894
|
+
if (!promoteForDrag()) {
|
|
2895
|
+
return { handled: false, reason: "unhandled" };
|
|
2896
|
+
}
|
|
2897
|
+
const dragOrigin = origin;
|
|
2898
|
+
if (!dragOrigin) {
|
|
2899
|
+
return { handled: false, reason: "unhandled" };
|
|
2900
|
+
}
|
|
2900
2901
|
// React still receives a click after a pointer drag. Suppress that
|
|
2901
2902
|
// trailing click so a moved text box does not immediately reopen
|
|
2902
2903
|
// inline editing.
|
|
@@ -2910,15 +2911,19 @@ export default function SlideEditor({
|
|
|
2910
2911
|
activeElement = clone;
|
|
2911
2912
|
}
|
|
2912
2913
|
applyObjectGeometry(activeElement, {
|
|
2913
|
-
...
|
|
2914
|
-
x:
|
|
2915
|
-
y:
|
|
2914
|
+
...dragOrigin,
|
|
2915
|
+
x: dragOrigin.x + gesture.canvasDelta.x,
|
|
2916
|
+
y: dragOrigin.y + gesture.canvasDelta.y,
|
|
2916
2917
|
});
|
|
2917
2918
|
const selector = getBuilderSelector(activeElement);
|
|
2918
2919
|
if (selector) selectElementForStyling(activeElement, selector);
|
|
2919
2920
|
return { handled: true };
|
|
2920
2921
|
},
|
|
2921
2922
|
commit: (gesture) => {
|
|
2923
|
+
if (!origin) {
|
|
2924
|
+
restore();
|
|
2925
|
+
return { handled: false, reason: "unhandled" };
|
|
2926
|
+
}
|
|
2922
2927
|
// Keeping Option pressed is the explicit duplicate commit. If it
|
|
2923
2928
|
// was released before drop, turn the gesture back into a normal move.
|
|
2924
2929
|
if (clone && !gesture.duplicate) {
|
|
@@ -2927,6 +2932,15 @@ export default function SlideEditor({
|
|
|
2927
2932
|
activeElement = element;
|
|
2928
2933
|
}
|
|
2929
2934
|
const html = readCurrentSlideContentHtml();
|
|
2935
|
+
// Markdown slides temporarily move their ReactMarkdown children into
|
|
2936
|
+
// an fmd canvas during promotion. Restore that live tree after
|
|
2937
|
+
// serialization and before the state write so React can reconcile
|
|
2938
|
+
// the switch to persisted raw HTML cleanly.
|
|
2939
|
+
if (restoreMarkdownTree) {
|
|
2940
|
+
removeFreeformLayoutSpacer();
|
|
2941
|
+
restoreMarkdownTree();
|
|
2942
|
+
restoreMarkdownTree = undefined;
|
|
2943
|
+
}
|
|
2930
2944
|
if (html !== null) {
|
|
2931
2945
|
onUpdateSlideRef.current({ content: html }, undefined, {
|
|
2932
2946
|
persistence: "immediate",
|
|
@@ -3441,7 +3455,7 @@ export default function SlideEditor({
|
|
|
3441
3455
|
const selected = resolveSelectedElement();
|
|
3442
3456
|
const shouldShowMoveCursor =
|
|
3443
3457
|
selected !== null &&
|
|
3444
|
-
|
|
3458
|
+
!isSlideCanvasShell(selected) &&
|
|
3445
3459
|
isWithinSlidesCanvasEdgeMoveBand(
|
|
3446
3460
|
selected.getBoundingClientRect(),
|
|
3447
3461
|
e.clientX,
|
|
@@ -3490,29 +3504,32 @@ export default function SlideEditor({
|
|
|
3490
3504
|
}
|
|
3491
3505
|
|
|
3492
3506
|
const selected = resolveSelectedElement();
|
|
3507
|
+
const clicked = findSelectableElement(target, slideContent);
|
|
3508
|
+
const dragTarget = resolveSlidesCanvasDragTarget(
|
|
3509
|
+
selected && !isSlideCanvasShell(selected) ? selected : null,
|
|
3510
|
+
clicked && !isSlideCanvasShell(clicked) ? clicked : null,
|
|
3511
|
+
);
|
|
3493
3512
|
const pointerIntent = resolveSlidesCanvasPointerIntent({
|
|
3494
|
-
hasSelectedObject:
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
targetContainsSelectedObject: selected
|
|
3499
|
-
? target.contains(selected)
|
|
3513
|
+
hasSelectedObject: dragTarget !== null,
|
|
3514
|
+
targetWithinSelectedObject: dragTarget?.contains(target) ?? false,
|
|
3515
|
+
targetContainsSelectedObject: dragTarget
|
|
3516
|
+
? target.contains(dragTarget)
|
|
3500
3517
|
: false,
|
|
3501
3518
|
pointerWithinMoveBand:
|
|
3502
|
-
|
|
3519
|
+
dragTarget !== null &&
|
|
3503
3520
|
isWithinSlidesCanvasEdgeMoveBand(
|
|
3504
|
-
|
|
3521
|
+
dragTarget.getBoundingClientRect(),
|
|
3505
3522
|
e.clientX,
|
|
3506
3523
|
e.clientY,
|
|
3507
3524
|
),
|
|
3508
3525
|
targetIsEditableText: Boolean(findSmartBlock(target, slideContent)),
|
|
3509
3526
|
});
|
|
3510
3527
|
if (
|
|
3511
|
-
|
|
3528
|
+
dragTarget &&
|
|
3512
3529
|
(pointerIntent === "move-object-body" ||
|
|
3513
3530
|
pointerIntent === "move-object-perimeter")
|
|
3514
3531
|
) {
|
|
3515
|
-
startElementDrag(e,
|
|
3532
|
+
startElementDrag(e, dragTarget, {
|
|
3516
3533
|
preserveClickWithoutMove: pointerIntent === "move-object-body",
|
|
3517
3534
|
});
|
|
3518
3535
|
return;
|
|
@@ -3598,6 +3615,7 @@ export default function SlideEditor({
|
|
|
3598
3615
|
candidates.forEach((node) => {
|
|
3599
3616
|
const el = node as HTMLElement;
|
|
3600
3617
|
if (el.classList.contains("fmd-layout-spacer")) return;
|
|
3618
|
+
if (isInlineTextElement(el)) return;
|
|
3601
3619
|
const id = el.getAttribute("data-builder-id");
|
|
3602
3620
|
if (!id) return;
|
|
3603
3621
|
// Skip the slide-content root itself if it ever got stamped
|
|
@@ -3744,6 +3762,21 @@ export default function SlideEditor({
|
|
|
3744
3762
|
],
|
|
3745
3763
|
);
|
|
3746
3764
|
|
|
3765
|
+
const clearCanvasSelection = useCallback(() => {
|
|
3766
|
+
if (editingEl) exitInlineEdit();
|
|
3767
|
+
if (multiSelection.size > 0) clearMultiSelection();
|
|
3768
|
+
setSelectedImg(null);
|
|
3769
|
+
setImageOverlay(null);
|
|
3770
|
+
clearSelectedElement();
|
|
3771
|
+
syncSelectionToAppState(null);
|
|
3772
|
+
}, [
|
|
3773
|
+
clearMultiSelection,
|
|
3774
|
+
clearSelectedElement,
|
|
3775
|
+
editingEl,
|
|
3776
|
+
exitInlineEdit,
|
|
3777
|
+
multiSelection.size,
|
|
3778
|
+
]);
|
|
3779
|
+
|
|
3747
3780
|
const handleSlideClick = useCallback(
|
|
3748
3781
|
(e: React.MouseEvent) => {
|
|
3749
3782
|
if (suppressNextClickRef.current) {
|
|
@@ -3776,9 +3809,7 @@ export default function SlideEditor({
|
|
|
3776
3809
|
// pointerdown already cleared it for non-additive drags, but a click
|
|
3777
3810
|
// with zero movement won't trigger pointerup with a real rect).
|
|
3778
3811
|
if (slideContent && isSlideWhitespaceTarget(target, slideContent)) {
|
|
3779
|
-
|
|
3780
|
-
clearSelectedElement();
|
|
3781
|
-
syncSelectionToAppState(null);
|
|
3812
|
+
clearCanvasSelection();
|
|
3782
3813
|
return;
|
|
3783
3814
|
}
|
|
3784
3815
|
|
|
@@ -3834,7 +3865,7 @@ export default function SlideEditor({
|
|
|
3834
3865
|
multiSelection,
|
|
3835
3866
|
applyMultiSelection,
|
|
3836
3867
|
clearMultiSelection,
|
|
3837
|
-
|
|
3868
|
+
clearCanvasSelection,
|
|
3838
3869
|
selectElementForStyling,
|
|
3839
3870
|
readOnly,
|
|
3840
3871
|
isHtmlSlide,
|
|
@@ -3842,6 +3873,16 @@ export default function SlideEditor({
|
|
|
3842
3873
|
],
|
|
3843
3874
|
);
|
|
3844
3875
|
|
|
3876
|
+
const handleCanvasBackgroundPointerDown = useCallback(
|
|
3877
|
+
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
3878
|
+
if (e.button !== 0) return;
|
|
3879
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
3880
|
+
if (target && slideCanvasRef.current?.contains(target)) return;
|
|
3881
|
+
clearCanvasSelection();
|
|
3882
|
+
},
|
|
3883
|
+
[clearCanvasSelection],
|
|
3884
|
+
);
|
|
3885
|
+
|
|
3845
3886
|
const handleSlideContextMenu = useCallback(
|
|
3846
3887
|
(e: React.MouseEvent) => {
|
|
3847
3888
|
const target = e.target as HTMLElement;
|
|
@@ -4096,11 +4137,9 @@ export default function SlideEditor({
|
|
|
4096
4137
|
const slideElementSelected =
|
|
4097
4138
|
!!selectedImg || !!editingEl || !!selectedStyleSnapshot;
|
|
4098
4139
|
|
|
4099
|
-
//
|
|
4100
|
-
//
|
|
4101
|
-
//
|
|
4102
|
-
// rather than an absolute coordinate, so treating "not static" as
|
|
4103
|
-
// draggable would move relative-positioned elements by the wrong amount.
|
|
4140
|
+
// Resize handles are only offered for elements with position: absolute.
|
|
4141
|
+
// Flow objects become absolute only after a real drag crosses the gesture
|
|
4142
|
+
// threshold, so a stationary click keeps its existing edit/select behavior.
|
|
4104
4143
|
const selectedForDrag =
|
|
4105
4144
|
selectedElementRect && !editingEl ? resolveSelectedElement() : null;
|
|
4106
4145
|
const isSelectedElementDraggable = selectedForDrag
|
|
@@ -4215,6 +4254,7 @@ export default function SlideEditor({
|
|
|
4215
4254
|
<div
|
|
4216
4255
|
ref={canvasTrackRef}
|
|
4217
4256
|
className="flex min-h-full w-max min-w-full items-center justify-center p-2 pt-14 sm:p-4 sm:pt-14 md:p-8 md:pt-16"
|
|
4257
|
+
onPointerDown={handleCanvasBackgroundPointerDown}
|
|
4218
4258
|
>
|
|
4219
4259
|
<div
|
|
4220
4260
|
ref={containerRef}
|
|
@@ -4232,6 +4272,7 @@ export default function SlideEditor({
|
|
|
4232
4272
|
onPointerDown={handleSlidePointerDown}
|
|
4233
4273
|
onPointerMove={handleSlidePointerMove}
|
|
4234
4274
|
onPointerLeave={clearEdgeMoveCursor}
|
|
4275
|
+
onDragStart={(event) => event.preventDefault()}
|
|
4235
4276
|
onDragOver={handleSlideDragOver}
|
|
4236
4277
|
onDrop={handleSlideDrop}
|
|
4237
4278
|
>
|
package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts
CHANGED
|
@@ -95,6 +95,24 @@ export type SlidesCanvasPointerIntent =
|
|
|
95
95
|
| "move-object-perimeter"
|
|
96
96
|
| "none";
|
|
97
97
|
|
|
98
|
+
/** Prefer the current selection when the pointer is inside it; otherwise the
|
|
99
|
+
* object under the pointer becomes the drag candidate, including on the first
|
|
100
|
+
* press before its click has updated selection state. */
|
|
101
|
+
export function resolveSlidesCanvasDragTarget(
|
|
102
|
+
selectedObject: HTMLElement | null,
|
|
103
|
+
pointerObject: HTMLElement | null,
|
|
104
|
+
): HTMLElement | null {
|
|
105
|
+
if (
|
|
106
|
+
selectedObject &&
|
|
107
|
+
pointerObject &&
|
|
108
|
+
(selectedObject.contains(pointerObject) ||
|
|
109
|
+
pointerObject.contains(selectedObject))
|
|
110
|
+
) {
|
|
111
|
+
return selectedObject;
|
|
112
|
+
}
|
|
113
|
+
return pointerObject ?? selectedObject;
|
|
114
|
+
}
|
|
115
|
+
|
|
98
116
|
/**
|
|
99
117
|
* Slides supplies hit testing and this policy decision, while the shared
|
|
100
118
|
* gesture controller owns threshold, coordinate, modifier, and resize math.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { findEnclosingList } from "./bullet-editing";
|
|
2
|
+
|
|
3
|
+
/** Inline markup changes presentation, but does not create a canvas target. */
|
|
4
|
+
const INLINE_TEXT_TAGS = new Set([
|
|
5
|
+
"SPAN",
|
|
6
|
+
"STRONG",
|
|
7
|
+
"EM",
|
|
8
|
+
"B",
|
|
9
|
+
"I",
|
|
10
|
+
"U",
|
|
11
|
+
"A",
|
|
12
|
+
"BR",
|
|
13
|
+
"CODE",
|
|
14
|
+
"SUB",
|
|
15
|
+
"SUP",
|
|
16
|
+
"MARK",
|
|
17
|
+
"SMALL",
|
|
18
|
+
"S",
|
|
19
|
+
"FONT",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function isInlineTextElement(element: Element): boolean {
|
|
23
|
+
return INLINE_TEXT_TAGS.has(element.tagName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function shouldStampBuilderId(element: HTMLElement): boolean {
|
|
27
|
+
return (
|
|
28
|
+
!element.classList.contains("fmd-layout-spacer") &&
|
|
29
|
+
!isInlineTextElement(element)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A text leaf is a block-level element whose children are text nodes or inline
|
|
35
|
+
* elements. Inline style runs are deliberately not text leaves themselves.
|
|
36
|
+
*/
|
|
37
|
+
export function isTextLeaf(element: HTMLElement): boolean {
|
|
38
|
+
if (!element || isInlineTextElement(element) || element.tagName === "IMG") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (element.classList.contains("fmd-img-placeholder")) return false;
|
|
42
|
+
// A user-placed text box stays editable after its content is deleted.
|
|
43
|
+
if (element.classList.contains("fmd-text-box")) return true;
|
|
44
|
+
if (!element.textContent?.trim()) return false;
|
|
45
|
+
for (const child of Array.from(element.children)) {
|
|
46
|
+
if (!isInlineTextElement(child)) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A container made only of text leaves or nested text groups. */
|
|
52
|
+
export function isSmartGroup(element: HTMLElement): boolean {
|
|
53
|
+
if (!element || isInlineTextElement(element) || element.tagName === "IMG") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (element.classList.contains("fmd-img-placeholder")) return false;
|
|
57
|
+
const children = Array.from(element.children);
|
|
58
|
+
if (children.length < 2) return false;
|
|
59
|
+
if (!element.textContent?.trim()) return false;
|
|
60
|
+
for (const child of children) {
|
|
61
|
+
const childElement = child as HTMLElement;
|
|
62
|
+
if (childElement.tagName === "IMG") return false;
|
|
63
|
+
if (childElement.classList.contains("fmd-img-placeholder")) return false;
|
|
64
|
+
if (!isTextLeaf(childElement) && !isSmartGroup(childElement)) return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Resolve a click inside inline markup to the containing editable text block. */
|
|
70
|
+
export function findSmartBlock(
|
|
71
|
+
target: HTMLElement,
|
|
72
|
+
root: HTMLElement,
|
|
73
|
+
): HTMLElement | null {
|
|
74
|
+
let element: HTMLElement | null = target;
|
|
75
|
+
while (element && root.contains(element)) {
|
|
76
|
+
if (isTextLeaf(element)) {
|
|
77
|
+
const list = findEnclosingList(element, root);
|
|
78
|
+
if (list) return list;
|
|
79
|
+
return element;
|
|
80
|
+
}
|
|
81
|
+
if (isSmartGroup(element)) return element;
|
|
82
|
+
element = element.parentElement;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
@@ -12,6 +12,7 @@ import { useSidebarCollapsed } from "@/hooks/use-sidebar-collapsed";
|
|
|
12
12
|
import { TAB_ID } from "@/lib/tab-id";
|
|
13
13
|
import { cn } from "@/lib/utils";
|
|
14
14
|
|
|
15
|
+
import { GoogleDriveConnectionCta } from "../editor/GoogleDriveConnectionCta";
|
|
15
16
|
import { AgentWorkIndicator } from "./AgentWorkIndicator";
|
|
16
17
|
import { Header } from "./Header";
|
|
17
18
|
import {
|
|
@@ -117,7 +118,9 @@ export function Layout({ children }: LayoutProps) {
|
|
|
117
118
|
scope={deckScope}
|
|
118
119
|
browserTabId={TAB_ID}
|
|
119
120
|
agentPageHref="/agent"
|
|
121
|
+
suppressFirstRunOnboarding={isSlidesEditorRoute(location.pathname)}
|
|
120
122
|
composerSlot={<CreativeContextComposerChip />}
|
|
123
|
+
threadFooterSlot={<GoogleDriveConnectionCta />}
|
|
121
124
|
>
|
|
122
125
|
<div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background text-foreground">
|
|
123
126
|
{sidebarOpen && (
|