@agent-native/core 0.141.7 → 0.144.1
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/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 +35 -38
- 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 +1 -1
- package/corpus/templates/analytics/actions/navigate.ts +4 -5
- package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
- package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
- package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
- package/corpus/templates/analytics/app/i18n-data.ts +680 -0
- 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/DataSources.tsx +4 -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/netlify.toml +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -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/dashboards-store.ts +227 -60
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +156 -45
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- 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 +28 -2
- package/corpus/templates/assets/.agents/skills/image-generation/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/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/sources.tsx +15 -7
- 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/hooks/use-events.ts +4 -1
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- 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/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/player/delete-recording-menu.tsx +14 -3
- package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
- 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/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.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/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/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/import-from-url.ts +1 -1
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
- package/corpus/templates/design/app/pages/Index.tsx +6 -2
- 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/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/AGENTS.md +8 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
- package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
- package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
- 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/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/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/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/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/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/slides/.agents/skills/create-deck/SKILL.md +48 -5
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
- 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 +17 -21
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/actions/export-pptx.ts +470 -36
- package/corpus/templates/slides/actions/get-deck.ts +12 -0
- package/corpus/templates/slides/actions/import-file.ts +175 -139
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
- package/corpus/templates/slides/actions/import-pptx.ts +193 -68
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +29 -3
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
- 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 +173 -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 +15 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
- 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/import-uploaded-deck.ts +89 -0
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +172 -15
- 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/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.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 +247 -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 +7 -4
- 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/lib/source-import.ts +167 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -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/dist/agent/production-agent.d.ts +17 -0
- package/dist/agent/production-agent.js +72 -1
- 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.js +123 -4
- package/dist/client/assistant-ui-recovery.d.ts +2 -1
- package/dist/client/assistant-ui-recovery.js +7 -3
- package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
- package/dist/client/org/TeamPage.js +1 -1
- package/dist/client/require-session.js +1 -1
- 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/settings/AccountSettingsCard.js +2 -1
- package/dist/collab/struct-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/file-upload/actions/upload-image.d.ts +1 -1
- 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/integrations/plugin.js +9 -6
- package/dist/jobs/scheduler.js +79 -23
- package/dist/localization/default-messages.d.ts +54 -0
- package/dist/localization/default-messages.js +57 -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 +73 -6
- package/dist/observability/routes.d.ts +5 -5
- package/dist/onboarding/app-profile.js +11 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.js +15 -1
- package/dist/scripts/call-agent.js +34 -4
- package/dist/secrets/routes.d.ts +3 -3
- 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 +47 -11
- 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/onboarding-html.d.ts +2 -0
- package/dist/server/onboarding-html.js +115 -29
- 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/transcribe-voice.d.ts +1 -1
- package/dist/shared/cache-control.d.ts +26 -2
- package/dist/shared/cache-control.js +43 -2
- package/dist/shared/password-policy.d.ts +3 -0
- package/dist/shared/password-policy.js +3 -0
- 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/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/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/creating-templates.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +38 -189
- package/docs/content/getting-started-database.mdx +80 -164
- package/docs/content/getting-started-pages.mdx +44 -155
- package/docs/content/getting-started.mdx +26 -106
- package/docs/content/skills-guide.mdx +13 -0
- 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/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/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/dist/ingestion/pptx.js
CHANGED
|
@@ -18,6 +18,13 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
18
18
|
const relationships = relationshipsXml
|
|
19
19
|
? parseRelationships(parseXml(relationshipsXml))
|
|
20
20
|
: new Map();
|
|
21
|
+
const slideMasterRelationship = [...relationships.values()].find((value) => value.type.endsWith("/slideMaster"));
|
|
22
|
+
const backgroundGrid = slideMasterRelationship
|
|
23
|
+
? await parseMasterGrid({
|
|
24
|
+
zip,
|
|
25
|
+
target: slideMasterRelationship.target,
|
|
26
|
+
})
|
|
27
|
+
: undefined;
|
|
21
28
|
const slidePaths = slideIds.flatMap((id) => {
|
|
22
29
|
const relationship = relationships.get(id);
|
|
23
30
|
if (!relationship)
|
|
@@ -46,61 +53,27 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
46
53
|
catch {
|
|
47
54
|
continue;
|
|
48
55
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
56
|
+
const metadata = parsePptxSlideMetadata(slide);
|
|
57
|
+
let elements = [];
|
|
51
58
|
const images = [];
|
|
52
59
|
const relationshipPath = slidePath.replace(/slides\/(slide\d+\.xml)/, "slides/_rels/$1.rels");
|
|
53
60
|
const slideRelationshipsXml = await zip
|
|
54
61
|
.file(relationshipPath)
|
|
55
62
|
?.async("string");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const relationship = slideRelationships.get(shape.embedId);
|
|
71
|
-
if (!relationship)
|
|
72
|
-
continue;
|
|
73
|
-
if (!relationship.type.includes("/image") &&
|
|
74
|
-
!/\.(png|jpe?g|gif|svg|webp|bmp|tiff?|emf|wmf)$/i.test(relationship.target)) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const imagePath = relationship.target.startsWith("/")
|
|
78
|
-
? relationship.target.slice(1)
|
|
79
|
-
: relationship.target.startsWith("../")
|
|
80
|
-
? `ppt/${relationship.target.replace(/^\.\.\//, "")}`
|
|
81
|
-
: `ppt/slides/${relationship.target}`;
|
|
82
|
-
const image = zip.file(imagePath);
|
|
83
|
-
if (!image)
|
|
84
|
-
continue;
|
|
85
|
-
const name = imagePath.split("/").at(-1) ?? "image";
|
|
86
|
-
const aspectRatio = shape.widthEmu && shape.heightEmu
|
|
87
|
-
? shape.widthEmu / shape.heightEmu
|
|
88
|
-
: undefined;
|
|
89
|
-
const fullBleed = Boolean(shape.widthEmu &&
|
|
90
|
-
shape.heightEmu &&
|
|
91
|
-
slideWidthEmu &&
|
|
92
|
-
slideHeightEmu &&
|
|
93
|
-
shape.widthEmu / slideWidthEmu >= 0.85 &&
|
|
94
|
-
shape.heightEmu / slideHeightEmu >= 0.85);
|
|
95
|
-
images.push({
|
|
96
|
-
data: new Uint8Array(await image.async("nodebuffer")),
|
|
97
|
-
mimeType: imageMimeType(name),
|
|
98
|
-
name,
|
|
99
|
-
aspectRatio,
|
|
100
|
-
fullBleed,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
63
|
+
const slideRelationships = slideRelationshipsXml
|
|
64
|
+
? parseRelationships(parseXml(slideRelationshipsXml))
|
|
65
|
+
: new Map();
|
|
66
|
+
elements = await parseSlideElements({
|
|
67
|
+
xml,
|
|
68
|
+
parseXml,
|
|
69
|
+
slide,
|
|
70
|
+
zip,
|
|
71
|
+
slideRelationships,
|
|
72
|
+
slideWidthEmu,
|
|
73
|
+
slideHeightEmu,
|
|
74
|
+
images,
|
|
75
|
+
});
|
|
76
|
+
const texts = flattenElementText(elements);
|
|
104
77
|
const number = slideNumber(slidePath);
|
|
105
78
|
const notesXml = await zip
|
|
106
79
|
.file(`ppt/notesSlides/notesSlide${number}.xml`)
|
|
@@ -119,8 +92,14 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
119
92
|
slides.push({
|
|
120
93
|
texts,
|
|
121
94
|
images,
|
|
95
|
+
elements,
|
|
96
|
+
widthEmu: slideWidthEmu,
|
|
97
|
+
heightEmu: slideHeightEmu,
|
|
98
|
+
backgroundColor: extractSlideBackgroundColor(slide),
|
|
99
|
+
...(backgroundGrid ? { backgroundGrid } : {}),
|
|
122
100
|
notes,
|
|
123
101
|
layoutHint: guessLayoutHint(texts, images.length > 0),
|
|
102
|
+
...metadata,
|
|
124
103
|
});
|
|
125
104
|
}
|
|
126
105
|
const firstSlide = slides[0]?.texts ?? [];
|
|
@@ -130,6 +109,573 @@ export async function parsePptxPresentation(fileBuffer) {
|
|
|
130
109
|
.slice(0, 200) || "Imported Presentation";
|
|
131
110
|
return { title, slides, theme };
|
|
132
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Google Slides exports decorative grids as connector shapes on the slide
|
|
114
|
+
* master instead of as a slide background. Preserve the repeated geometry as
|
|
115
|
+
* metadata so the HTML renderer can reproduce it without making the lines
|
|
116
|
+
* editable slide objects.
|
|
117
|
+
*/
|
|
118
|
+
async function parseMasterGrid(args) {
|
|
119
|
+
const path = args.target.startsWith("/")
|
|
120
|
+
? args.target.slice(1)
|
|
121
|
+
: `ppt/${args.target.replace(/^\.\.\//, "")}`;
|
|
122
|
+
const xml = await args.zip.file(path)?.async("string");
|
|
123
|
+
if (!xml)
|
|
124
|
+
return undefined;
|
|
125
|
+
const connectors = xml.match(/<p:cxnSp\b[\s\S]*?<\/p:cxnSp>/gi) ?? [];
|
|
126
|
+
const candidates = connectors.flatMap((fragment) => {
|
|
127
|
+
const color = fragment.match(/<a:solidFill>\s*<a:srgbClr\s+val="([0-9a-f]{6})"/i)?.[1];
|
|
128
|
+
const transform = fragment.match(/<a:xfrm[^>]*>\s*<a:off\s+x="(-?\d+)"\s+y="(-?\d+)"\s*\/>\s*<a:ext\s+cx="(-?\d+)"\s+cy="(-?\d+)"/i);
|
|
129
|
+
const lineWidth = fragment.match(/<a:ln[^>]*\bw="(\d+)"/i)?.[1];
|
|
130
|
+
if (!color || !transform || !lineWidth)
|
|
131
|
+
return [];
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
color: color.toLowerCase(),
|
|
135
|
+
x: Number(transform[1]),
|
|
136
|
+
y: Number(transform[2]),
|
|
137
|
+
width: Number(transform[3]),
|
|
138
|
+
height: Number(transform[4]),
|
|
139
|
+
lineWidth: Number(lineWidth),
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
});
|
|
143
|
+
if (candidates.length < 20)
|
|
144
|
+
return undefined;
|
|
145
|
+
const colorCounts = new Map();
|
|
146
|
+
for (const candidate of candidates) {
|
|
147
|
+
colorCounts.set(candidate.color, (colorCounts.get(candidate.color) ?? 0) + 1);
|
|
148
|
+
}
|
|
149
|
+
const [color] = [...colorCounts.entries()].sort((a, b) => b[1] - a[1])[0] ?? [];
|
|
150
|
+
if (!color)
|
|
151
|
+
return undefined;
|
|
152
|
+
const xPositions = [
|
|
153
|
+
...new Set(candidates
|
|
154
|
+
.filter((candidate) => candidate.color === color)
|
|
155
|
+
.map((candidate) => candidate.x)),
|
|
156
|
+
].sort((a, b) => a - b);
|
|
157
|
+
const gaps = xPositions
|
|
158
|
+
.slice(1)
|
|
159
|
+
.map((value, index) => value - xPositions[index])
|
|
160
|
+
.filter((value) => value > 100_000);
|
|
161
|
+
if (gaps.length < 3)
|
|
162
|
+
return undefined;
|
|
163
|
+
gaps.sort((a, b) => a - b);
|
|
164
|
+
const stepXEmu = gaps[Math.floor(gaps.length / 2)];
|
|
165
|
+
if (!stepXEmu)
|
|
166
|
+
return undefined;
|
|
167
|
+
const offsetXEmu = xPositions.find((value) => value >= 0) ?? xPositions[0];
|
|
168
|
+
const lineWidthEmu = Math.max(1, Math.round(candidates
|
|
169
|
+
.filter((candidate) => candidate.color === color)
|
|
170
|
+
.reduce((sum, candidate) => sum + candidate.lineWidth, 0) /
|
|
171
|
+
candidates.filter((candidate) => candidate.color === color).length));
|
|
172
|
+
// The same repeated connector lattice is used for both axes in the Google
|
|
173
|
+
// export. Its horizontal phase is the master group's first repeated offset.
|
|
174
|
+
// Keeping the phase relative to the detected step also works for custom
|
|
175
|
+
// slide sizes that preserve the source grid's square-cell geometry.
|
|
176
|
+
const stepYEmu = stepXEmu;
|
|
177
|
+
const offsetYEmu = Math.round(stepYEmu * 0.9);
|
|
178
|
+
return {
|
|
179
|
+
color: `#${color}`,
|
|
180
|
+
stepXEmu,
|
|
181
|
+
stepYEmu,
|
|
182
|
+
offsetXEmu,
|
|
183
|
+
offsetYEmu,
|
|
184
|
+
lineWidthEmu,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const SHAPE_ELEMENT_NAMES = new Set([
|
|
188
|
+
"sp",
|
|
189
|
+
"pic",
|
|
190
|
+
"grpSp",
|
|
191
|
+
"cxnSp",
|
|
192
|
+
"graphicFrame",
|
|
193
|
+
]);
|
|
194
|
+
async function parseSlideElements(args) {
|
|
195
|
+
const fragments = extractDirectShapeFragments(args.xml, "spTree");
|
|
196
|
+
const elements = [];
|
|
197
|
+
const context = {
|
|
198
|
+
originX: 0,
|
|
199
|
+
originY: 0,
|
|
200
|
+
scaleX: 1,
|
|
201
|
+
scaleY: 1,
|
|
202
|
+
rotation: 0,
|
|
203
|
+
};
|
|
204
|
+
for (const fragment of fragments) {
|
|
205
|
+
const parsed = await parseShapeFragment(fragment, {
|
|
206
|
+
...args,
|
|
207
|
+
context,
|
|
208
|
+
});
|
|
209
|
+
if (parsed.length > 0)
|
|
210
|
+
elements.push(...parsed);
|
|
211
|
+
}
|
|
212
|
+
// Some authors use a picture fill on the slide background instead of a
|
|
213
|
+
// picture shape. Keep it in the same ordered scene graph at the back.
|
|
214
|
+
const backgroundEmbedId = extractBackgroundFillEmbedId(args.slide);
|
|
215
|
+
if (backgroundEmbedId) {
|
|
216
|
+
const backgroundRelationship = args.slideRelationships.get(backgroundEmbedId);
|
|
217
|
+
if (backgroundRelationship) {
|
|
218
|
+
const image = await loadPptxImage({
|
|
219
|
+
relationship: backgroundRelationship,
|
|
220
|
+
zip: args.zip,
|
|
221
|
+
slideWidthEmu: args.slideWidthEmu,
|
|
222
|
+
slideHeightEmu: args.slideHeightEmu,
|
|
223
|
+
x: 0,
|
|
224
|
+
y: 0,
|
|
225
|
+
width: args.slideWidthEmu ?? 0,
|
|
226
|
+
height: args.slideHeightEmu ?? 0,
|
|
227
|
+
});
|
|
228
|
+
if (image) {
|
|
229
|
+
args.images.unshift(image.image);
|
|
230
|
+
elements.unshift(image.element);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return elements;
|
|
235
|
+
}
|
|
236
|
+
async function parseShapeFragment(fragment, args) {
|
|
237
|
+
const parsed = record(args.parseXml(fragment));
|
|
238
|
+
if (!parsed)
|
|
239
|
+
return [];
|
|
240
|
+
const entry = [...SHAPE_ELEMENT_NAMES].find((name) => parsed[`p:${name}`] != null);
|
|
241
|
+
if (!entry)
|
|
242
|
+
return [];
|
|
243
|
+
const node = record(parsed[`p:${entry}`]);
|
|
244
|
+
if (!node)
|
|
245
|
+
return [];
|
|
246
|
+
if (entry === "grpSp") {
|
|
247
|
+
const groupTransform = readTransform(node, "p:grpSpPr");
|
|
248
|
+
const groupXfrm = record(record(node["p:grpSpPr"])?.["a:xfrm"]);
|
|
249
|
+
const childOffset = readPoint(groupXfrm?.["a:chOff"]);
|
|
250
|
+
const childExtent = readPoint(groupXfrm?.["a:chExt"]);
|
|
251
|
+
const groupScaleX = childExtent.x > 0 ? groupTransform.width / childExtent.x : 1;
|
|
252
|
+
const groupScaleY = childExtent.y > 0 ? groupTransform.height / childExtent.y : 1;
|
|
253
|
+
const nextContext = {
|
|
254
|
+
originX: args.context.originX +
|
|
255
|
+
args.context.scaleX * (groupTransform.x - childOffset.x * groupScaleX),
|
|
256
|
+
originY: args.context.originY +
|
|
257
|
+
args.context.scaleY * (groupTransform.y - childOffset.y * groupScaleY),
|
|
258
|
+
scaleX: args.context.scaleX * groupScaleX,
|
|
259
|
+
scaleY: args.context.scaleY * groupScaleY,
|
|
260
|
+
rotation: args.context.rotation + (groupTransform.rotation ?? 0),
|
|
261
|
+
};
|
|
262
|
+
const output = [];
|
|
263
|
+
for (const child of extractDirectShapeFragments(fragment, "grpSp")) {
|
|
264
|
+
output.push(...(await parseShapeFragment(child, {
|
|
265
|
+
...args,
|
|
266
|
+
context: nextContext,
|
|
267
|
+
})));
|
|
268
|
+
}
|
|
269
|
+
return output;
|
|
270
|
+
}
|
|
271
|
+
const transform = applyTransform(readTransform(node, "p:spPr"), args.context);
|
|
272
|
+
const id = readShapeId(node);
|
|
273
|
+
const name = readShapeName(node);
|
|
274
|
+
const placeholderType = stringValue(record(record(record(node["p:nvSpPr"])?.["p:nvPr"])?.["p:ph"])?.["@_type"]);
|
|
275
|
+
const shapeProperties = record(node["p:spPr"]);
|
|
276
|
+
const text = parseTextBody(node);
|
|
277
|
+
const fill = parseShapeFill(shapeProperties);
|
|
278
|
+
const line = parseShapeLine(shapeProperties);
|
|
279
|
+
const shapeType = stringValue(record(shapeProperties?.["a:prstGeom"])?.["@_prst"]);
|
|
280
|
+
if (entry === "pic") {
|
|
281
|
+
const embedId = stringValue(record(record(node["p:blipFill"])?.["a:blip"])?.["@_r:embed"]);
|
|
282
|
+
if (!embedId)
|
|
283
|
+
return [];
|
|
284
|
+
const relationship = args.slideRelationships.get(embedId);
|
|
285
|
+
if (!relationship)
|
|
286
|
+
return [];
|
|
287
|
+
const image = await loadPptxImage({
|
|
288
|
+
relationship,
|
|
289
|
+
zip: args.zip,
|
|
290
|
+
slideWidthEmu: args.slideWidthEmu,
|
|
291
|
+
slideHeightEmu: args.slideHeightEmu,
|
|
292
|
+
x: transform.x,
|
|
293
|
+
y: transform.y,
|
|
294
|
+
width: transform.width,
|
|
295
|
+
height: transform.height,
|
|
296
|
+
crop: parseImageCrop(node),
|
|
297
|
+
});
|
|
298
|
+
if (!image)
|
|
299
|
+
return [];
|
|
300
|
+
args.images.push(image.image);
|
|
301
|
+
return [
|
|
302
|
+
{
|
|
303
|
+
id,
|
|
304
|
+
name,
|
|
305
|
+
kind: "image",
|
|
306
|
+
...transform,
|
|
307
|
+
image: image.image,
|
|
308
|
+
},
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
const hasText = text.some((paragraph) => paragraph.runs.length > 0);
|
|
312
|
+
if (hasText) {
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
id,
|
|
316
|
+
name,
|
|
317
|
+
...(placeholderType ? { placeholderType } : {}),
|
|
318
|
+
kind: "text",
|
|
319
|
+
...transform,
|
|
320
|
+
shapeType,
|
|
321
|
+
...(fill ? { fill } : {}),
|
|
322
|
+
...(line ? { lineColor: line.color, lineWidth: line.width } : {}),
|
|
323
|
+
...parseTextBoxProperties(node),
|
|
324
|
+
paragraphs: text,
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
if (fill || line) {
|
|
329
|
+
return [
|
|
330
|
+
{
|
|
331
|
+
id,
|
|
332
|
+
name,
|
|
333
|
+
kind: "shape",
|
|
334
|
+
...transform,
|
|
335
|
+
shapeType,
|
|
336
|
+
...(fill ? { fill } : {}),
|
|
337
|
+
...(line ? { lineColor: line.color, lineWidth: line.width } : {}),
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
function extractDirectShapeFragments(xml, container) {
|
|
344
|
+
const containerMatch = new RegExp(`<p:${container}\\b[^>]*>`, "i").exec(xml);
|
|
345
|
+
if (!containerMatch)
|
|
346
|
+
return [];
|
|
347
|
+
const containerEnd = findMatchingXmlTag(xml, containerMatch.index, container);
|
|
348
|
+
if (containerEnd < 0)
|
|
349
|
+
return [];
|
|
350
|
+
const start = containerMatch.index + containerMatch[0].length;
|
|
351
|
+
const end = containerEnd;
|
|
352
|
+
const tagPattern = /<\/?(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)\b[^>]*>/g;
|
|
353
|
+
tagPattern.lastIndex = start;
|
|
354
|
+
const stack = [];
|
|
355
|
+
const fragments = [];
|
|
356
|
+
let shapeStart = -1;
|
|
357
|
+
let match;
|
|
358
|
+
while ((match = tagPattern.exec(xml)) && match.index < end) {
|
|
359
|
+
const token = match[0];
|
|
360
|
+
const localName = match[1];
|
|
361
|
+
const isClosing = token.startsWith("</");
|
|
362
|
+
const isSelfClosing = /\/\s*>$/.test(token);
|
|
363
|
+
if (isClosing) {
|
|
364
|
+
if (stack.length > 0)
|
|
365
|
+
stack.pop();
|
|
366
|
+
if (stack.length === 0 && shapeStart >= 0) {
|
|
367
|
+
fragments.push(xml.slice(shapeStart, match.index + token.length));
|
|
368
|
+
shapeStart = -1;
|
|
369
|
+
}
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (stack.length === 0 && SHAPE_ELEMENT_NAMES.has(localName)) {
|
|
373
|
+
shapeStart = match.index;
|
|
374
|
+
}
|
|
375
|
+
if (!isSelfClosing)
|
|
376
|
+
stack.push(localName);
|
|
377
|
+
}
|
|
378
|
+
return fragments;
|
|
379
|
+
}
|
|
380
|
+
function findMatchingXmlTag(xml, start, localName) {
|
|
381
|
+
const tagPattern = /<\/?(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)\b[^>]*>/g;
|
|
382
|
+
tagPattern.lastIndex = start;
|
|
383
|
+
let depth = 0;
|
|
384
|
+
let match;
|
|
385
|
+
while ((match = tagPattern.exec(xml))) {
|
|
386
|
+
const token = match[0];
|
|
387
|
+
if (match[1] !== localName || /\/\s*>$/.test(token))
|
|
388
|
+
continue;
|
|
389
|
+
if (token.startsWith("</")) {
|
|
390
|
+
depth -= 1;
|
|
391
|
+
if (depth === 0)
|
|
392
|
+
return match.index;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
depth += 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return -1;
|
|
399
|
+
}
|
|
400
|
+
function readShapeId(node) {
|
|
401
|
+
const cNvPr = record(record(node["p:nvSpPr"])?.["p:cNvPr"]) ??
|
|
402
|
+
record(record(node["p:nvPicPr"])?.["p:cNvPr"]);
|
|
403
|
+
return (stringValue(cNvPr?.["@_id"]) ??
|
|
404
|
+
`shape-${Math.random().toString(36).slice(2)}`);
|
|
405
|
+
}
|
|
406
|
+
function readShapeName(node) {
|
|
407
|
+
return stringValue(record(record(node["p:nvSpPr"])?.["p:cNvPr"])?.["@_name"]);
|
|
408
|
+
}
|
|
409
|
+
function readPoint(value) {
|
|
410
|
+
const point = record(value);
|
|
411
|
+
return {
|
|
412
|
+
x: Number(point?.["@_x"]) || 0,
|
|
413
|
+
y: Number(point?.["@_y"]) || 0,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function readExtent(value) {
|
|
417
|
+
const extent = record(value);
|
|
418
|
+
return {
|
|
419
|
+
x: Number(extent?.["@_cx"]) || 0,
|
|
420
|
+
y: Number(extent?.["@_cy"]) || 0,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function readTransform(node, key) {
|
|
424
|
+
const xfrm = record(record(node[key])?.["a:xfrm"]);
|
|
425
|
+
const off = readPoint(xfrm?.["a:off"]);
|
|
426
|
+
const ext = readExtent(xfrm?.["a:ext"]);
|
|
427
|
+
const rawRotation = Number(xfrm?.["@_rot"]);
|
|
428
|
+
return {
|
|
429
|
+
x: off.x,
|
|
430
|
+
y: off.y,
|
|
431
|
+
width: ext.x,
|
|
432
|
+
height: ext.y,
|
|
433
|
+
...(Number.isFinite(rawRotation) && rawRotation !== 0
|
|
434
|
+
? { rotation: rawRotation / 60000 }
|
|
435
|
+
: {}),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function applyTransform(transform, context) {
|
|
439
|
+
return {
|
|
440
|
+
x: context.originX + transform.x * context.scaleX,
|
|
441
|
+
y: context.originY + transform.y * context.scaleY,
|
|
442
|
+
width: transform.width * context.scaleX,
|
|
443
|
+
height: transform.height * context.scaleY,
|
|
444
|
+
...(transform.rotation || context.rotation
|
|
445
|
+
? { rotation: (transform.rotation ?? 0) + context.rotation }
|
|
446
|
+
: {}),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function parseTextBody(node) {
|
|
450
|
+
const txBody = record(node["p:txBody"]);
|
|
451
|
+
if (!txBody)
|
|
452
|
+
return [];
|
|
453
|
+
return asArray(txBody["a:p"]).map((rawParagraph) => {
|
|
454
|
+
const paragraph = record(rawParagraph);
|
|
455
|
+
const pPr = record(paragraph?.["a:pPr"]);
|
|
456
|
+
const runs = [];
|
|
457
|
+
for (const rawRun of asArray(paragraph?.["a:r"])) {
|
|
458
|
+
const run = record(rawRun);
|
|
459
|
+
const content = innerText(run?.["a:t"]);
|
|
460
|
+
if (content) {
|
|
461
|
+
runs.push({
|
|
462
|
+
content,
|
|
463
|
+
...runProperties(record(run?.["a:rPr"]), {}),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
for (const rawField of asArray(paragraph?.["a:fld"])) {
|
|
468
|
+
const field = record(rawField);
|
|
469
|
+
const content = innerText(field?.["a:t"]);
|
|
470
|
+
if (content) {
|
|
471
|
+
runs.push({
|
|
472
|
+
content,
|
|
473
|
+
...runProperties(record(field?.["a:rPr"]), {}),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const bullet = record(pPr?.["a:buChar"]);
|
|
478
|
+
const bulletColor = parseColor(record(pPr?.["a:buClr"]));
|
|
479
|
+
const bulletFont = stringValue(record(pPr?.["a:buFont"])?.["@_typeface"]);
|
|
480
|
+
const bulletSize = Number(record(pPr?.["a:buSzPts"])?.["@_val"]);
|
|
481
|
+
const lineSpacing = parseParagraphSpacing(pPr?.["a:lnSpc"]);
|
|
482
|
+
const spaceBeforePt = parsePoints(pPr?.["a:spcBef"]);
|
|
483
|
+
const spaceAfterPt = parsePoints(pPr?.["a:spcAft"]);
|
|
484
|
+
const alignment = mapAlignment(stringValue(pPr?.["@_algn"]));
|
|
485
|
+
return {
|
|
486
|
+
runs,
|
|
487
|
+
...(alignment ? { alignment } : {}),
|
|
488
|
+
...(bullet?.["@_char"] && !pPr?.["a:buNone"]
|
|
489
|
+
? { bulletChar: String(bullet["@_char"]) }
|
|
490
|
+
: {}),
|
|
491
|
+
...(bulletColor ? { bulletColor } : {}),
|
|
492
|
+
...(bulletFont ? { bulletFontFamily: bulletFont } : {}),
|
|
493
|
+
...(Number.isFinite(bulletSize) && bulletSize > 0
|
|
494
|
+
? { bulletSize: bulletSize / 100 }
|
|
495
|
+
: {}),
|
|
496
|
+
...(Number.isFinite(Number(pPr?.["@_lvl"]))
|
|
497
|
+
? { level: Number(pPr?.["@_lvl"]) }
|
|
498
|
+
: {}),
|
|
499
|
+
...(Number.isFinite(Number(pPr?.["@_marL"]))
|
|
500
|
+
? { marginLeftEmu: Number(pPr?.["@_marL"]) }
|
|
501
|
+
: {}),
|
|
502
|
+
...(Number.isFinite(Number(pPr?.["@_indent"]))
|
|
503
|
+
? { indentEmu: Number(pPr?.["@_indent"]) }
|
|
504
|
+
: {}),
|
|
505
|
+
...(lineSpacing !== undefined ? { lineSpacing } : {}),
|
|
506
|
+
...(spaceBeforePt !== undefined ? { spaceBeforePt } : {}),
|
|
507
|
+
...(spaceAfterPt !== undefined ? { spaceAfterPt } : {}),
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function parseTextBoxProperties(node) {
|
|
512
|
+
const bodyPr = record(record(node["p:txBody"])?.["a:bodyPr"]);
|
|
513
|
+
if (!bodyPr)
|
|
514
|
+
return {};
|
|
515
|
+
const anchor = stringValue(bodyPr["@_anchor"]);
|
|
516
|
+
return {
|
|
517
|
+
padding: {
|
|
518
|
+
left: Number(bodyPr["@_lIns"]) || 0,
|
|
519
|
+
right: Number(bodyPr["@_rIns"]) || 0,
|
|
520
|
+
top: Number(bodyPr["@_tIns"]) || 0,
|
|
521
|
+
bottom: Number(bodyPr["@_bIns"]) || 0,
|
|
522
|
+
},
|
|
523
|
+
...(anchor === "ctr"
|
|
524
|
+
? { verticalAlign: "middle" }
|
|
525
|
+
: anchor === "b"
|
|
526
|
+
? { verticalAlign: "bottom" }
|
|
527
|
+
: { verticalAlign: "top" }),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function parseShapeFill(shapeProperties) {
|
|
531
|
+
if (!shapeProperties)
|
|
532
|
+
return undefined;
|
|
533
|
+
if (shapeProperties["a:noFill"] !== undefined)
|
|
534
|
+
return undefined;
|
|
535
|
+
return parseColor(record(shapeProperties["a:solidFill"]));
|
|
536
|
+
}
|
|
537
|
+
function parseShapeLine(shapeProperties) {
|
|
538
|
+
const line = record(shapeProperties?.["a:ln"]);
|
|
539
|
+
if (!line || line["a:noFill"] !== undefined)
|
|
540
|
+
return undefined;
|
|
541
|
+
const color = parseColor(record(line["a:solidFill"]));
|
|
542
|
+
if (!color)
|
|
543
|
+
return undefined;
|
|
544
|
+
const width = Number(line["@_w"]);
|
|
545
|
+
return {
|
|
546
|
+
color,
|
|
547
|
+
...(Number.isFinite(width) && width > 0 ? { width } : {}),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function parseColor(value) {
|
|
551
|
+
if (!value)
|
|
552
|
+
return undefined;
|
|
553
|
+
const rgb = stringValue(record(value["a:srgbClr"])?.["@_val"]);
|
|
554
|
+
if (rgb)
|
|
555
|
+
return `#${rgb}`;
|
|
556
|
+
const scheme = stringValue(record(value["a:schemeClr"])?.["@_val"]);
|
|
557
|
+
if (!scheme)
|
|
558
|
+
return undefined;
|
|
559
|
+
const pptxDarkColor = "#000000"; // guard:allow-raw-color - PPTX dark scheme fallback
|
|
560
|
+
const pptxLightColor = "#ffffff"; // guard:allow-raw-color - PPTX light scheme fallback
|
|
561
|
+
const fallback = {
|
|
562
|
+
dk1: pptxDarkColor,
|
|
563
|
+
dk2: pptxDarkColor,
|
|
564
|
+
lt1: pptxLightColor,
|
|
565
|
+
lt2: pptxLightColor,
|
|
566
|
+
};
|
|
567
|
+
return fallback[scheme];
|
|
568
|
+
}
|
|
569
|
+
function parseParagraphSpacing(value) {
|
|
570
|
+
const node = record(value);
|
|
571
|
+
const percent = Number(record(node?.["a:spcPct"])?.["@_val"]);
|
|
572
|
+
if (Number.isFinite(percent) && percent > 0)
|
|
573
|
+
return percent / 100000;
|
|
574
|
+
return parsePoints(node?.["a:spcPts"]);
|
|
575
|
+
}
|
|
576
|
+
function parsePoints(value) {
|
|
577
|
+
const node = record(value);
|
|
578
|
+
const points = Number(node?.["@_val"]);
|
|
579
|
+
return Number.isFinite(points) && points >= 0 ? points / 100 : undefined;
|
|
580
|
+
}
|
|
581
|
+
function mapAlignment(value) {
|
|
582
|
+
if (value === "ctr")
|
|
583
|
+
return "center";
|
|
584
|
+
if (value === "r")
|
|
585
|
+
return "right";
|
|
586
|
+
if (value === "just")
|
|
587
|
+
return "justify";
|
|
588
|
+
if (value === "l")
|
|
589
|
+
return "left";
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
function parseImageCrop(node) {
|
|
593
|
+
const srcRect = record(record(node["p:blipFill"])?.["a:srcRect"]);
|
|
594
|
+
if (!srcRect)
|
|
595
|
+
return undefined;
|
|
596
|
+
const left = Number(srcRect["@_l"]) || 0;
|
|
597
|
+
const top = Number(srcRect["@_t"]) || 0;
|
|
598
|
+
const right = Number(srcRect["@_r"]) || 0;
|
|
599
|
+
const bottom = Number(srcRect["@_b"]) || 0;
|
|
600
|
+
return left || top || right || bottom
|
|
601
|
+
? {
|
|
602
|
+
left: left / 100000,
|
|
603
|
+
top: top / 100000,
|
|
604
|
+
right: right / 100000,
|
|
605
|
+
bottom: bottom / 100000,
|
|
606
|
+
}
|
|
607
|
+
: undefined;
|
|
608
|
+
}
|
|
609
|
+
async function loadPptxImage(args) {
|
|
610
|
+
if (!args.relationship.type.includes("/image") &&
|
|
611
|
+
!/\.(png|jpe?g|gif|svg|webp|bmp|tiff?|emf|wmf)$/i.test(args.relationship.target)) {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const imagePath = args.relationship.target.startsWith("/")
|
|
615
|
+
? args.relationship.target.slice(1)
|
|
616
|
+
: args.relationship.target.startsWith("../")
|
|
617
|
+
? `ppt/${args.relationship.target.replace(/^\.\.\//, "")}`
|
|
618
|
+
: `ppt/slides/${args.relationship.target}`;
|
|
619
|
+
const imageFile = args.zip.file(imagePath);
|
|
620
|
+
if (!imageFile)
|
|
621
|
+
return null;
|
|
622
|
+
const name = imagePath.split("/").at(-1) ?? "image";
|
|
623
|
+
const image = {
|
|
624
|
+
data: new Uint8Array(await imageFile.async("nodebuffer")),
|
|
625
|
+
mimeType: imageMimeType(name),
|
|
626
|
+
name,
|
|
627
|
+
aspectRatio: args.width && args.height ? args.width / args.height : undefined,
|
|
628
|
+
fullBleed: Boolean(args.width &&
|
|
629
|
+
args.height &&
|
|
630
|
+
args.slideWidthEmu &&
|
|
631
|
+
args.slideHeightEmu &&
|
|
632
|
+
args.width / args.slideWidthEmu >= 0.85 &&
|
|
633
|
+
args.height / args.slideHeightEmu >= 0.85),
|
|
634
|
+
...(args.crop ? { crop: args.crop } : {}),
|
|
635
|
+
};
|
|
636
|
+
return {
|
|
637
|
+
image,
|
|
638
|
+
element: {
|
|
639
|
+
id: `image-${name}-${args.x}-${args.y}`,
|
|
640
|
+
name,
|
|
641
|
+
kind: "image",
|
|
642
|
+
x: args.x,
|
|
643
|
+
y: args.y,
|
|
644
|
+
width: args.width,
|
|
645
|
+
height: args.height,
|
|
646
|
+
image,
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function flattenElementText(elements) {
|
|
651
|
+
const output = [];
|
|
652
|
+
const textElements = elements.filter((element) => element.kind === "text" && element.paragraphs);
|
|
653
|
+
for (const [elementIndex, element] of textElements.entries()) {
|
|
654
|
+
const paragraphs = element.paragraphs ?? [];
|
|
655
|
+
for (const [paragraphIndex, paragraph] of paragraphs.entries()) {
|
|
656
|
+
output.push(...paragraph.runs);
|
|
657
|
+
if (paragraphIndex < paragraphs.length - 1)
|
|
658
|
+
output.push({ content: "\n" });
|
|
659
|
+
}
|
|
660
|
+
if (elementIndex < textElements.length - 1)
|
|
661
|
+
output.push({ content: "\n" });
|
|
662
|
+
}
|
|
663
|
+
return output;
|
|
664
|
+
}
|
|
665
|
+
function extractSlideBackgroundColor(value) {
|
|
666
|
+
const root = record(value);
|
|
667
|
+
const cSld = record(record(root?.["p:sld"])?.["p:cSld"] ?? root?.["p:cSld"]);
|
|
668
|
+
const bgPr = record(record(cSld?.["p:bg"])?.["p:bgPr"]);
|
|
669
|
+
return parseColor(record(bgPr?.["a:solidFill"]));
|
|
670
|
+
}
|
|
671
|
+
export function parsePptxSlideMetadata(value) {
|
|
672
|
+
const slide = record(value)?.["p:sld"] ?? value;
|
|
673
|
+
const transition = parsePptxTransition(slide);
|
|
674
|
+
return {
|
|
675
|
+
...(transition ? { transition } : {}),
|
|
676
|
+
...(detectSplitByParagraph(slide) ? { splitByParagraph: true } : {}),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
133
679
|
async function parseTheme(zip, parseXml) {
|
|
134
680
|
const xml = await zip.file("ppt/theme/theme1.xml")?.async("string");
|
|
135
681
|
if (!xml)
|
|
@@ -158,6 +704,17 @@ function collectTextRuns(value, runs, inherited = {}) {
|
|
|
158
704
|
const node = record(value);
|
|
159
705
|
if (!node)
|
|
160
706
|
return;
|
|
707
|
+
const paragraphs = asArray(node["a:p"]);
|
|
708
|
+
if (paragraphs.length > 0) {
|
|
709
|
+
paragraphs.forEach((paragraph, index) => {
|
|
710
|
+
const before = runs.length;
|
|
711
|
+
collectTextRuns(paragraph, runs, inherited);
|
|
712
|
+
if (runs.length > before && index < paragraphs.length - 1) {
|
|
713
|
+
runs.push({ content: "\n" });
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
161
718
|
for (const raw of asArray(node["a:r"])) {
|
|
162
719
|
const run = record(raw);
|
|
163
720
|
const content = innerText(run?.["a:t"]);
|
|
@@ -175,158 +732,60 @@ function collectTextRuns(value, runs, inherited = {}) {
|
|
|
175
732
|
for (const [key, child] of Object.entries(node)) {
|
|
176
733
|
if (key.startsWith("@_") || key === "a:r" || key === "a:t")
|
|
177
734
|
continue;
|
|
178
|
-
|
|
735
|
+
const items = asArray(child);
|
|
736
|
+
items.forEach((item, index) => {
|
|
737
|
+
const before = runs.length;
|
|
179
738
|
collectTextRuns(item, runs, inherited);
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Recursively find every embedded picture in a slide, in document order:
|
|
184
|
-
* plain `p:pic` shapes, and `p:sp` autoshapes whose fill is a picture
|
|
185
|
-
* (common for "photo cutout" shapes and full-bleed decorative rectangles).
|
|
186
|
-
* A group shape (`p:grpSp`) defines its own local coordinate space for its
|
|
187
|
-
* children (`chOff`/`chExt`) that can be scaled arbitrarily relative to the
|
|
188
|
-
* group's own placed size on the slide, so each level of nesting multiplies
|
|
189
|
-
* a running scale factor into the child sizes below it — without that, a
|
|
190
|
-
* picture inside a resized group would report its unscaled design-time
|
|
191
|
-
* size instead of how large it actually appears on the slide.
|
|
192
|
-
*/
|
|
193
|
-
function collectPictureShapes(value, out, scaleX = 1, scaleY = 1) {
|
|
194
|
-
const node = record(value);
|
|
195
|
-
if (!node)
|
|
196
|
-
return;
|
|
197
|
-
for (const [key, child] of Object.entries(node)) {
|
|
198
|
-
if (key.startsWith("@_"))
|
|
199
|
-
continue;
|
|
200
|
-
if (key === "p:pic") {
|
|
201
|
-
for (const picNode of asArray(child)) {
|
|
202
|
-
const pic = record(picNode);
|
|
203
|
-
const blip = record(record(pic?.["p:blipFill"])?.["a:blip"]);
|
|
204
|
-
out.push(scaledShape(stringValue(blip?.["@_r:embed"]), extFromSpPr(pic), scaleX, scaleY));
|
|
205
|
-
}
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if (key === "p:sp") {
|
|
209
|
-
for (const spNode of asArray(child)) {
|
|
210
|
-
const sp = record(spNode);
|
|
211
|
-
const blip = record(record(record(sp?.["p:spPr"])?.["a:blipFill"])?.["a:blip"]);
|
|
212
|
-
const embedId = stringValue(blip?.["@_r:embed"]);
|
|
213
|
-
if (embedId) {
|
|
214
|
-
out.push(scaledShape(embedId, extFromSpPr(sp), scaleX, scaleY));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (key === "p:grpSp") {
|
|
220
|
-
for (const groupNode of asArray(child)) {
|
|
221
|
-
const scale = groupChildScale(record(groupNode), scaleX, scaleY);
|
|
222
|
-
collectPictureShapes(groupNode, out, scale.x, scale.y);
|
|
739
|
+
if (key === "p:sp" && index < items.length - 1 && runs.length > before) {
|
|
740
|
+
runs.push({ content: "\n" });
|
|
223
741
|
}
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
for (const item of asArray(child)) {
|
|
227
|
-
collectPictureShapes(item, out, scaleX, scaleY);
|
|
228
|
-
}
|
|
742
|
+
});
|
|
229
743
|
}
|
|
230
744
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const ext = record(xfrm?.["a:ext"]);
|
|
249
|
-
const chExt = record(xfrm?.["a:chExt"]);
|
|
250
|
-
const extCx = Number(ext?.["@_cx"]);
|
|
251
|
-
const extCy = Number(ext?.["@_cy"]);
|
|
252
|
-
const chExtCx = Number(chExt?.["@_cx"]);
|
|
253
|
-
const chExtCy = Number(chExt?.["@_cy"]);
|
|
254
|
-
let groupScaleX = Number.isFinite(extCx) && Number.isFinite(chExtCx) && chExtCx > 0
|
|
255
|
-
? extCx / chExtCx
|
|
256
|
-
: 1;
|
|
257
|
-
let groupScaleY = Number.isFinite(extCy) && Number.isFinite(chExtCy) && chExtCy > 0
|
|
258
|
-
? extCy / chExtCy
|
|
259
|
-
: 1;
|
|
260
|
-
if (isOddQuarterTurn(Number(xfrm?.["@_rot"]))) {
|
|
261
|
-
[groupScaleX, groupScaleY] = [groupScaleY, groupScaleX];
|
|
745
|
+
const PPTX_TRANSITION_MAP = {
|
|
746
|
+
"p:fade": "fade",
|
|
747
|
+
"p:zoom": "zoom",
|
|
748
|
+
"p:push": "slide",
|
|
749
|
+
"p:wipe": "slide",
|
|
750
|
+
"p:split": "slide",
|
|
751
|
+
"p:cut": "instant",
|
|
752
|
+
};
|
|
753
|
+
function parsePptxTransition(value) {
|
|
754
|
+
const node = record(value);
|
|
755
|
+
const transition = record(node?.["p:transition"]);
|
|
756
|
+
if (!transition)
|
|
757
|
+
return undefined;
|
|
758
|
+
for (const key of Object.keys(transition)) {
|
|
759
|
+
const mapped = PPTX_TRANSITION_MAP[key];
|
|
760
|
+
if (mapped)
|
|
761
|
+
return mapped;
|
|
262
762
|
}
|
|
263
|
-
return
|
|
763
|
+
return undefined;
|
|
264
764
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
765
|
+
function detectSplitByParagraph(value) {
|
|
766
|
+
let clickParagraphRanges = 0;
|
|
767
|
+
walk(value, false);
|
|
768
|
+
return clickParagraphRanges > 1;
|
|
769
|
+
function walk(nodeValue, clickContext) {
|
|
770
|
+
const node = record(nodeValue);
|
|
771
|
+
if (!node)
|
|
772
|
+
return;
|
|
773
|
+
const nodeType = stringValue(node["@_nodeType"]);
|
|
774
|
+
const event = stringValue(node["@_evt"]);
|
|
775
|
+
const nextClickContext = clickContext ||
|
|
776
|
+
nodeType === "clickEffect" ||
|
|
777
|
+
nodeType === "clickPar" ||
|
|
778
|
+
event === "onClick";
|
|
779
|
+
if (nextClickContext) {
|
|
780
|
+
clickParagraphRanges += asArray(node["p:pRg"]).length;
|
|
781
|
+
}
|
|
782
|
+
for (const [key, child] of Object.entries(node)) {
|
|
783
|
+
if (key.startsWith("@_") || key === "p:pRg")
|
|
784
|
+
continue;
|
|
785
|
+
for (const item of asArray(child))
|
|
786
|
+
walk(item, nextClickContext);
|
|
787
|
+
}
|
|
280
788
|
}
|
|
281
|
-
return isOddQuarterTurn(Number(xfrm?.["@_rot"]))
|
|
282
|
-
? { cx: cy, cy: cx }
|
|
283
|
-
: { cx, cy };
|
|
284
|
-
}
|
|
285
|
-
function scaledShape(embedId, size, scaleX, scaleY) {
|
|
286
|
-
return {
|
|
287
|
-
embedId,
|
|
288
|
-
widthEmu: size.cx !== undefined ? size.cx * scaleX : undefined,
|
|
289
|
-
heightEmu: size.cy !== undefined ? size.cy * scaleY : undefined,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* fast-xml-parser groups sibling elements by tag name, so a slide whose
|
|
294
|
-
* picture-bearing shapes alternate types on the page (e.g. `p:sp`, `p:pic`,
|
|
295
|
-
* `p:sp`) comes out of `collectPictureShapes` re-sorted by tag rather than
|
|
296
|
-
* by the order they actually appear — every `p:sp` gets visited before any
|
|
297
|
-
* `p:pic`, even if a `p:pic` came first in the file. Re-derive the true
|
|
298
|
-
* order from the raw XML text instead: `<a:blip r:embed="...">` occurrences
|
|
299
|
-
* appear in exactly document order regardless of nesting, so they can be
|
|
300
|
-
* used to restore the author's original front-to-back ordering (which
|
|
301
|
-
* matters because downstream rendering treats the first image as primary).
|
|
302
|
-
*/
|
|
303
|
-
function reorderByDocumentPosition(shapes, xml) {
|
|
304
|
-
const order = [];
|
|
305
|
-
const blipPattern = /<a:blip\b[^>]*\br:embed=(?:"([^"]+)"|'([^']+)')/g;
|
|
306
|
-
let match;
|
|
307
|
-
while ((match = blipPattern.exec(xml)))
|
|
308
|
-
order.push(match[1] ?? match[2]);
|
|
309
|
-
// The same relationship id can legitimately be reused across multiple
|
|
310
|
-
// placements (e.g. a repeated icon), so looking up a single fixed index
|
|
311
|
-
// per id would assign every reused shape the position of its first XML
|
|
312
|
-
// occurrence. Instead give each id a queue of its occurrence positions
|
|
313
|
-
// and consume one per shape, in the order shapes were collected.
|
|
314
|
-
const occurrences = new Map();
|
|
315
|
-
order.forEach((embedId, index) => {
|
|
316
|
-
const queue = occurrences.get(embedId);
|
|
317
|
-
if (queue)
|
|
318
|
-
queue.push(index);
|
|
319
|
-
else
|
|
320
|
-
occurrences.set(embedId, [index]);
|
|
321
|
-
});
|
|
322
|
-
const positions = shapes.map((shape) => {
|
|
323
|
-
const queue = shape.embedId ? occurrences.get(shape.embedId) : undefined;
|
|
324
|
-
return queue && queue.length > 0 ? queue.shift() : -1;
|
|
325
|
-
});
|
|
326
|
-
const sortedIndices = shapes.map((_, i) => i);
|
|
327
|
-
sortedIndices.sort((a, b) => positions[a] - positions[b]);
|
|
328
|
-
const sorted = sortedIndices.map((i) => shapes[i]);
|
|
329
|
-
shapes.splice(0, shapes.length, ...sorted);
|
|
330
789
|
}
|
|
331
790
|
/** Read the embed relationship id of a slide's background picture fill (`p:cSld/p:bg/p:bgPr/a:blipFill/a:blip`), if any. */
|
|
332
791
|
function extractBackgroundFillEmbedId(slide) {
|
|
@@ -336,29 +795,14 @@ function extractBackgroundFillEmbedId(slide) {
|
|
|
336
795
|
const blip = record(record(bgPr?.["a:blipFill"])?.["a:blip"]);
|
|
337
796
|
return stringValue(blip?.["@_r:embed"]);
|
|
338
797
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Add the slide's background picture fill, if any, as a synthetic
|
|
341
|
-
* full-slide-sized shape. It's unshifted to the front rather than appended:
|
|
342
|
-
* downstream rendering only ever uses the first image in the list, and a
|
|
343
|
-
* background fill is the slide's base layer — the primary visual — so a
|
|
344
|
-
* smaller foreground picture (a logo, an icon) must not bump it out of that
|
|
345
|
-
* slot.
|
|
346
|
-
*/
|
|
347
|
-
function addBackgroundFillShape(slide, pictureShapes, slideWidthEmu, slideHeightEmu) {
|
|
348
|
-
const embedId = extractBackgroundFillEmbedId(slide);
|
|
349
|
-
if (!embedId)
|
|
350
|
-
return;
|
|
351
|
-
pictureShapes.unshift({
|
|
352
|
-
embedId,
|
|
353
|
-
widthEmu: slideWidthEmu,
|
|
354
|
-
heightEmu: slideHeightEmu,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
798
|
function runProperties(value, inherited) {
|
|
358
799
|
if (!value)
|
|
359
800
|
return inherited;
|
|
360
801
|
const size = Number(value["@_sz"]);
|
|
361
802
|
const rgb = stringValue(record(record(value["a:solidFill"])?.["a:srgbClr"])?.["@_val"]);
|
|
803
|
+
const fontFamily = stringValue(record(value["a:latin"])?.["@_typeface"]) ??
|
|
804
|
+
stringValue(record(value["a:ea"])?.["@_typeface"]) ??
|
|
805
|
+
stringValue(record(value["a:cs"])?.["@_typeface"]);
|
|
362
806
|
return {
|
|
363
807
|
...inherited,
|
|
364
808
|
...(value["@_b"] === "1" || value["@_b"] === 1 || value["@_b"] === true
|
|
@@ -369,6 +813,8 @@ function runProperties(value, inherited) {
|
|
|
369
813
|
: {}),
|
|
370
814
|
...(Number.isFinite(size) && size > 0 ? { fontSize: size / 100 } : {}),
|
|
371
815
|
...(rgb ? { color: `#${rgb}` } : {}),
|
|
816
|
+
...(fontFamily ? { fontFamily } : {}),
|
|
817
|
+
...(value["@_u"] && value["@_u"] !== "none" ? { underline: true } : {}),
|
|
372
818
|
};
|
|
373
819
|
}
|
|
374
820
|
function parseRelationships(value) {
|
|
@@ -422,6 +868,7 @@ async function loadPptxDependencies() {
|
|
|
422
868
|
const parser = new xmlModule.XMLParser({
|
|
423
869
|
ignoreAttributes: false,
|
|
424
870
|
attributeNamePrefix: "@_",
|
|
871
|
+
trimValues: false,
|
|
425
872
|
});
|
|
426
873
|
return {
|
|
427
874
|
loadZip: (data) => zipModule.default.loadAsync(data),
|