simple-module-pagebuilder 0.0.3__tar.gz → 0.0.4__tar.gz
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.
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/PKG-INFO +33 -1
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/README.md +32 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/package.json +1 -1
- simple_module_pagebuilder-0.0.4/pagebuilder/components/lazyBlock.tsx +58 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/background-media.tsx +5 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/content-media.tsx +13 -1
- simple_module_pagebuilder-0.0.4/pagebuilder/components/widgets/_shared/image-srcset.ts +55 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/index.ts +7 -0
- simple_module_pagebuilder-0.0.4/pagebuilder/components/widgets/hero-widget-props.ts +33 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/hero-widget.tsx +21 -29
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/media-object-layout.ts +2 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/media-object-render.tsx +5 -1
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/media-object-widget.tsx +4 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/seo.py +4 -1
- simple_module_pagebuilder-0.0.4/pagebuilder/media_files.py +128 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/media_service.py +4 -1
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/module.py +8 -3
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/service/__init__.py +32 -2
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/settings.py +5 -2
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pyproject.toml +1 -1
- simple_module_pagebuilder-0.0.4/tests/test_media_files.py +137 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/.gitignore +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/__init__.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/DiffSummary.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/ScheduledBadge.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/StatusBadge.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blockRegistry.test.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blockRegistry.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Button.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Columns.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Heading.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Image.test.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Image.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/MediaPicker.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Spacer.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/blocks/Text.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/editor/PageEditorToolbar.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/editor/RevisionHistoryPanel.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/editor/SchedulePanel.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/editor/SeoSettingsPanel.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/layoutPuckConfig.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/MediaFilters.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/MediaFolderSidebar.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/MediaGrid.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/MediaHeader.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/MediaUploadQueue.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/media/types.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/migrateContent.test.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/migrateContent.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/puckConfig.test.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/puckConfig.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgetCatalog.test.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgetCatalog.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/accent-text.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/disclosure-chevron.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/eyebrow-split-section.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/link-arrow.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/rich-text.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_internal/solid-icons.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/cta-button.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/empty-placeholder.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/eyebrow-text.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/heading.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/mask.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/section.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/social-icon.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/_shared/text-link.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/accordion-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/alert-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/app-store-badges-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/article-cards-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/article-cards-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/call-to-action-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/call-to-action-styles.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/call-to-action-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/carousel-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/centered-hero-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/colored-action-list-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/column-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/contact-cards-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/contact-form-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/contact-form-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/container-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/definition-list-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/divider-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/eyebrow-section-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/faq-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/faq-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-card-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-cards-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-cards-styles.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-cards-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-grid-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/feature-showcase-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/gallery-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/grid-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/html-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/iframe-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/leaderboard-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/list-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/logo-cloud-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/markdown-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/media-object-text.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/newsletter-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/page-header-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/prose-section-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/quote-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/row-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/section-intro-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/signup-banner-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/simple-action-list-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/site-footer-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/site-header-nav.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/site-header-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/social-banner-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/stats-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/steps-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/story-cards-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/table-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/tabs-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/tags-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/testimonial-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/timeline-layout.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/timeline-render.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/timeline-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/under-construction-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/video-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/components/widgets/welcome-widget.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/contracts/__init__.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/contracts/events.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/contracts/schemas.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/conversion/blocks-to-html.test.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/conversion/blocks-to-html.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/conversion/index.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/conversion/types.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/deps.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/diff.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/__init__.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/__init__.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/_deps.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/layout.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/pages.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/revisions.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/uploads.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/api/workflow.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/views.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/checkbox-field.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/file-picker/file-gallery.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/file-picker/image-picker-field.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/file-picker/index.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/file-picker/types.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/index.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/fields/media-adapter.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/useAutosave.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/useEditorForm.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/useMediaUploads.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/usePageRevisions.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/usePageSchedule.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/hooks/usePageWorkflow.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/layout_service.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/media_images.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/media_queries.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/models.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/LayoutEditor.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/MediaLibrary.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/PageEditor.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/PageList.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/PendingReview.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/pages/PublicPage.tsx +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/permissions.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/py.typed +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/security.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/service/_common.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/service/_revisions.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/service/_workflow.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/services.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/static/widgets-base.css +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/translation/apply.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/translation/extract.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/translation/index.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/translation/translatable-fields.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/translation/translation.test.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/api.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/datetime.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/decorative-image.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/editorSnapshot.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/markdown.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/mediaApi.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/mediaFormat.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/pagesApi.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/parse-list.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/request.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/slugify.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/types.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/utils/widgetUtils.ts +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/conftest.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_approval_workflow.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_auth_gating.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_csrf.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_image_processing.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_media_security.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_module.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_pages_api.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_public_routes.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_public_view_headers.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_public_viewer.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_revision_notes_and_diff.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_revisions_api.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_scheduled_publish.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_seo_meta.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_site_layout.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_sitemap_robots.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tests/test_uploads_api.py +0 -0
- {simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simple_module_pagebuilder
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Drag-and-drop visual page builder for simple_module apps
|
|
5
5
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python_modules
|
|
6
6
|
Author-email: Anto Subash <antosubash@live.com>
|
|
@@ -81,6 +81,38 @@ Once installed and migrated, sign in and open `/pagebuilder`:
|
|
|
81
81
|
Every transition writes a revision, so `/pagebuilder/{id}/edit` can compare any
|
|
82
82
|
two revisions and restore either one as a draft.
|
|
83
83
|
|
|
84
|
+
## Contributing blocks from another module
|
|
85
|
+
|
|
86
|
+
A module ships Puck blocks by calling `registerPuckBlocks` from its
|
|
87
|
+
`puck-blocks.ts`; the host imports every module's registration eagerly at app
|
|
88
|
+
start, so the registry is populated before the first render.
|
|
89
|
+
|
|
90
|
+
Because registration is eager, a block that statically imports a heavy render
|
|
91
|
+
component puts that component's whole dependency graph in the entry chunk —
|
|
92
|
+
on every page of the site, for every visitor. Measured on a consuming site, a
|
|
93
|
+
map block's static import put maplibre-gl (1 MB minified, 62% of the bundle)
|
|
94
|
+
in front of visitors who never opened the map. Register heavy blocks with
|
|
95
|
+
`lazyBlock` instead, which keeps fields and defaults eager but loads the
|
|
96
|
+
component the first time a page actually renders the block:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { lazyBlock } from '@simple-module-py/pagebuilder/pagebuilder/components/lazyBlock';
|
|
100
|
+
import { registerPuckBlocks } from '@simple-module-py/pagebuilder/pagebuilder/components/blockRegistry';
|
|
101
|
+
|
|
102
|
+
registerPuckBlocks({
|
|
103
|
+
blocks: {
|
|
104
|
+
AtlasMap: lazyBlock(
|
|
105
|
+
() => import('./components/AtlasMap').then((m) => m.AtlasMapEmbed),
|
|
106
|
+
{ label: 'Atlas map', fields: { /* … */ }, defaultProps: { /* … */ } },
|
|
107
|
+
(props) => <div className={heightClass[props.height]} aria-busy="true" />,
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Light blocks (text, cards, lists) can keep their static imports — the split
|
|
114
|
+
only pays for itself when the component drags in something big.
|
|
115
|
+
|
|
84
116
|
## Migrations
|
|
85
117
|
|
|
86
118
|
This module ships **no** migrations — that is the framework convention. Its
|
|
@@ -59,6 +59,38 @@ Once installed and migrated, sign in and open `/pagebuilder`:
|
|
|
59
59
|
Every transition writes a revision, so `/pagebuilder/{id}/edit` can compare any
|
|
60
60
|
two revisions and restore either one as a draft.
|
|
61
61
|
|
|
62
|
+
## Contributing blocks from another module
|
|
63
|
+
|
|
64
|
+
A module ships Puck blocks by calling `registerPuckBlocks` from its
|
|
65
|
+
`puck-blocks.ts`; the host imports every module's registration eagerly at app
|
|
66
|
+
start, so the registry is populated before the first render.
|
|
67
|
+
|
|
68
|
+
Because registration is eager, a block that statically imports a heavy render
|
|
69
|
+
component puts that component's whole dependency graph in the entry chunk —
|
|
70
|
+
on every page of the site, for every visitor. Measured on a consuming site, a
|
|
71
|
+
map block's static import put maplibre-gl (1 MB minified, 62% of the bundle)
|
|
72
|
+
in front of visitors who never opened the map. Register heavy blocks with
|
|
73
|
+
`lazyBlock` instead, which keeps fields and defaults eager but loads the
|
|
74
|
+
component the first time a page actually renders the block:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { lazyBlock } from '@simple-module-py/pagebuilder/pagebuilder/components/lazyBlock';
|
|
78
|
+
import { registerPuckBlocks } from '@simple-module-py/pagebuilder/pagebuilder/components/blockRegistry';
|
|
79
|
+
|
|
80
|
+
registerPuckBlocks({
|
|
81
|
+
blocks: {
|
|
82
|
+
AtlasMap: lazyBlock(
|
|
83
|
+
() => import('./components/AtlasMap').then((m) => m.AtlasMapEmbed),
|
|
84
|
+
{ label: 'Atlas map', fields: { /* … */ }, defaultProps: { /* … */ } },
|
|
85
|
+
(props) => <div className={heightClass[props.height]} aria-busy="true" />,
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Light blocks (text, cards, lists) can keep their static imports — the split
|
|
92
|
+
only pays for itself when the component drags in something big.
|
|
93
|
+
|
|
62
94
|
## Migrations
|
|
63
95
|
|
|
64
96
|
This module ships **no** migrations — that is the framework convention. Its
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register a Puck block whose render component loads on demand (issue #16).
|
|
3
|
+
*
|
|
4
|
+
* Block registration is eager by design — the host imports every module's
|
|
5
|
+
* `puck-blocks.ts` before first render so the registry is populated when a
|
|
6
|
+
* page reads the Puck config. But a registration that statically imports its
|
|
7
|
+
* render component drags the component's whole dependency graph into the
|
|
8
|
+
* entry chunk, on every page of the site. Measured on a consuming site, a
|
|
9
|
+
* map block put maplibre-gl — 1 MB minified, 62% of the bundle — in front
|
|
10
|
+
* of every visitor.
|
|
11
|
+
*
|
|
12
|
+
* `lazyBlock` keeps the cheap parts eager (fields, defaults, labels) and
|
|
13
|
+
* loads the component the first time a page actually renders the block:
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* registerPuckBlocks({
|
|
17
|
+
* blocks: {
|
|
18
|
+
* CanopyAtlasMap: lazyBlock(
|
|
19
|
+
* () => import('./components/CanopyAtlasMap').then((m) => m.CanopyAtlasMapEmbed),
|
|
20
|
+
* {
|
|
21
|
+
* label: 'Canopy Atlas map',
|
|
22
|
+
* fields: { height: { type: 'select', options: HEIGHTS } },
|
|
23
|
+
* defaultProps: { height: 'screen' },
|
|
24
|
+
* },
|
|
25
|
+
* (props) => <div className={heightClass[props.height]} aria-busy="true" />,
|
|
26
|
+
* ),
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { type ComponentType, lazy, type ReactNode, Suspense } from 'react';
|
|
33
|
+
|
|
34
|
+
import type { AnyBlock } from './blockRegistry';
|
|
35
|
+
|
|
36
|
+
// Typed via AnyBlock rather than a precise ComponentConfig<P>: Puck's config
|
|
37
|
+
// generic carries constraints a pass-through helper cannot restate, and the
|
|
38
|
+
// registry these blocks land in erases the props type anyway.
|
|
39
|
+
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous block props, same as AnyBlock
|
|
40
|
+
type AnyProps = Record<string, any>;
|
|
41
|
+
|
|
42
|
+
export function lazyBlock<P extends AnyProps>(
|
|
43
|
+
load: () => Promise<ComponentType<P>>,
|
|
44
|
+
config: Omit<AnyBlock, 'render'>,
|
|
45
|
+
/** Rendered while the chunk downloads — size it like the block to avoid
|
|
46
|
+
* layout shift. Defaults to nothing. */
|
|
47
|
+
fallback?: (props: P) => ReactNode,
|
|
48
|
+
): AnyBlock {
|
|
49
|
+
const Lazy = lazy(async () => ({ default: await load() }));
|
|
50
|
+
return {
|
|
51
|
+
...config,
|
|
52
|
+
render: (props: AnyProps) => (
|
|
53
|
+
<Suspense fallback={fallback ? fallback(props as P) : null}>
|
|
54
|
+
<Lazy {...(props as P)} />
|
|
55
|
+
</Suspense>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn, decorativeAriaProps } from '../../../utils/widgetUtils';
|
|
2
|
+
import { SIZES_FULL, srcsetAttrs } from './image-srcset';
|
|
2
3
|
|
|
3
4
|
export type BackgroundMediaOverlay = 'none' | 'subtle' | 'dark';
|
|
4
5
|
export type BackgroundMediaFallback = 'gradient-mesh' | 'solid';
|
|
@@ -15,6 +16,8 @@ export type BackgroundMediaProps = {
|
|
|
15
16
|
* anchors to the top; centered, the sky is cut away). Omitted = centered.
|
|
16
17
|
*/
|
|
17
18
|
objectPosition?: string;
|
|
19
|
+
/** Variant srcset recorded at pick time (see _shared/image-srcset.ts). */
|
|
20
|
+
imageSrcset?: string;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
// The "dark" scrim strength is tenant-tunable: GCA's Figma hero is nearly
|
|
@@ -41,6 +44,7 @@ export function BackgroundMedia({
|
|
|
41
44
|
overlay = 'none',
|
|
42
45
|
fallback = 'gradient-mesh',
|
|
43
46
|
objectPosition,
|
|
47
|
+
imageSrcset,
|
|
44
48
|
}: BackgroundMediaProps) {
|
|
45
49
|
// data-pb-* attributes are stable selectors for tests and CMS-side analytics.
|
|
46
50
|
return (
|
|
@@ -48,6 +52,7 @@ export function BackgroundMedia({
|
|
|
48
52
|
{imageUrl ? (
|
|
49
53
|
<img
|
|
50
54
|
src={imageUrl}
|
|
55
|
+
{...srcsetAttrs(imageSrcset, SIZES_FULL)}
|
|
51
56
|
alt={imageAlt ?? ''}
|
|
52
57
|
{...decorativeAriaProps(imageAlt)}
|
|
53
58
|
loading="lazy"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn, decorativeAriaProps } from '../../../utils/widgetUtils';
|
|
2
|
+
import { srcsetAttrs } from './image-srcset';
|
|
2
3
|
|
|
3
4
|
export type ContentMediaAspect = 'square' | 'video' | 'portrait';
|
|
4
5
|
|
|
@@ -6,6 +7,10 @@ export type ContentMediaProps = {
|
|
|
6
7
|
imageUrl: string;
|
|
7
8
|
imageAlt: string;
|
|
8
9
|
aspect?: ContentMediaAspect;
|
|
10
|
+
/** Variant srcset recorded at pick time (see _shared/image-srcset.ts). */
|
|
11
|
+
imageSrcset?: string;
|
|
12
|
+
/** `sizes` for the srcset; callers in split layouts pass SIZES_HALF. */
|
|
13
|
+
sizes?: string;
|
|
9
14
|
};
|
|
10
15
|
|
|
11
16
|
const ASPECT_CLASSES: Record<ContentMediaAspect, string> = {
|
|
@@ -23,13 +28,20 @@ const DOTTED_STYLE = {
|
|
|
23
28
|
backgroundSize: '16px 16px, 100% 100%',
|
|
24
29
|
} as const;
|
|
25
30
|
|
|
26
|
-
export function ContentMedia({
|
|
31
|
+
export function ContentMedia({
|
|
32
|
+
imageUrl,
|
|
33
|
+
imageAlt,
|
|
34
|
+
aspect = 'video',
|
|
35
|
+
imageSrcset,
|
|
36
|
+
sizes = '100vw',
|
|
37
|
+
}: ContentMediaProps) {
|
|
27
38
|
const wrapperCls = cn('w-full rounded-xl overflow-hidden', ASPECT_CLASSES[aspect], 'shadow-xl');
|
|
28
39
|
if (imageUrl) {
|
|
29
40
|
return (
|
|
30
41
|
<div className={wrapperCls}>
|
|
31
42
|
<img
|
|
32
43
|
src={imageUrl}
|
|
44
|
+
{...srcsetAttrs(imageSrcset, sizes)}
|
|
33
45
|
alt={imageAlt ?? ''}
|
|
34
46
|
{...decorativeAriaProps(imageAlt)}
|
|
35
47
|
loading="lazy"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Responsive-image support for picker-backed widgets (issue #15).
|
|
2
|
+
*
|
|
3
|
+
* The media library generates WebP variants at upload, but widgets that
|
|
4
|
+
* store a bare `imageUrl` string shipped the full-size original to every
|
|
5
|
+
* viewport. These helpers let a widget persist the variant srcset next to
|
|
6
|
+
* the URL the same way the Image block does: `resolveImageSrcset` runs as
|
|
7
|
+
* Puck's `resolveData` when the picked URL changes, and the value lands in
|
|
8
|
+
* block data so the public render needs no lookup at all. Old content
|
|
9
|
+
* without the prop renders exactly as before.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { buildSrcset, lookupAsset } from '../../../utils/mediaApi';
|
|
13
|
+
|
|
14
|
+
/** Full-bleed imagery (background heroes, banners). */
|
|
15
|
+
export const SIZES_FULL = '100vw';
|
|
16
|
+
|
|
17
|
+
/** Half-width split layouts (side hero, media object) — full width stacked. */
|
|
18
|
+
export const SIZES_HALF = '(min-width: 1024px) 50vw, 100vw';
|
|
19
|
+
|
|
20
|
+
/** Field for the persisted srcset — visible so an author can override it. */
|
|
21
|
+
export const imageSrcsetField = {
|
|
22
|
+
type: 'textarea',
|
|
23
|
+
label: 'Image srcset (auto-filled from the media library)',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
/** Spreadable `<img>` attributes; nothing when no srcset is recorded. */
|
|
27
|
+
export function srcsetAttrs(
|
|
28
|
+
srcset: string | undefined,
|
|
29
|
+
sizes: string,
|
|
30
|
+
): { srcSet: string; sizes: string } | Record<string, never> {
|
|
31
|
+
return srcset ? { srcSet: srcset, sizes } : {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type SrcsetProps = { imageUrl: string; imageSrcset?: string };
|
|
35
|
+
|
|
36
|
+
/** `resolveData` for any widget with `imageUrl` + `imageSrcset` props.
|
|
37
|
+
* `changed` is typed to just the key read here — Puck's own param type
|
|
38
|
+
* (which also carries `id: string`) stays assignable. */
|
|
39
|
+
export async function resolveImageSrcset<P extends SrcsetProps>(
|
|
40
|
+
{ props }: { props: P },
|
|
41
|
+
{ changed }: { changed: { imageUrl?: unknown } },
|
|
42
|
+
): Promise<{ props: P }> {
|
|
43
|
+
if (!changed.imageUrl) return { props };
|
|
44
|
+
if (!props.imageUrl) return { props: { ...props, imageSrcset: '' } };
|
|
45
|
+
let asset: Awaited<ReturnType<typeof lookupAsset>>;
|
|
46
|
+
try {
|
|
47
|
+
asset = await lookupAsset(props.imageUrl);
|
|
48
|
+
} catch {
|
|
49
|
+
// Network failure mid-edit shouldn't break the editor — keep the picked
|
|
50
|
+
// URL and let the image serve without variants.
|
|
51
|
+
return { props };
|
|
52
|
+
}
|
|
53
|
+
if (!asset) return { props };
|
|
54
|
+
return { props: { ...props, imageSrcset: buildSrcset(asset) } };
|
|
55
|
+
}
|
|
@@ -26,6 +26,13 @@ export type {
|
|
|
26
26
|
HeadingTone,
|
|
27
27
|
} from './heading';
|
|
28
28
|
export { Heading } from './heading';
|
|
29
|
+
export {
|
|
30
|
+
imageSrcsetField,
|
|
31
|
+
resolveImageSrcset,
|
|
32
|
+
SIZES_FULL,
|
|
33
|
+
SIZES_HALF,
|
|
34
|
+
srcsetAttrs,
|
|
35
|
+
} from './image-srcset';
|
|
29
36
|
export {
|
|
30
37
|
EYEBROW_CASE_STYLE,
|
|
31
38
|
eyebrowTextStyle,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Prop shape for HeroWidget — extracted so hero-widget.tsx stays under the
|
|
2
|
+
* repo's 300-line cap. Re-exported from there, so importers are unaffected. */
|
|
3
|
+
|
|
4
|
+
export type HeroWidgetProps = {
|
|
5
|
+
eyebrow: string;
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle: string;
|
|
8
|
+
logoUrl: string;
|
|
9
|
+
logoAlt: string;
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
imageAlt: string;
|
|
12
|
+
/** Variant srcset recorded at pick time (see _shared/image-srcset.ts). */
|
|
13
|
+
imageSrcset?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Organic mask (SVG url, from the tenant's content) applied to the side
|
|
16
|
+
* image in the right/left layouts. Empty = the standard rounded media.
|
|
17
|
+
*/
|
|
18
|
+
imageMaskUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* "native" renders supplied transparent artwork exactly as-is — no mask, no
|
|
21
|
+
* crop — for a photo already cut to its final organic shape. Preferred over
|
|
22
|
+
* `imageMaskUrl`, which re-derives the silhouette from a hand-kept SVG.
|
|
23
|
+
*/
|
|
24
|
+
imageShape?: 'rounded' | 'native';
|
|
25
|
+
imagePosition: 'background' | 'right' | 'left';
|
|
26
|
+
primaryLabel: string;
|
|
27
|
+
primaryHref: string;
|
|
28
|
+
secondaryLabel: string;
|
|
29
|
+
secondaryHref: string;
|
|
30
|
+
overlay: boolean;
|
|
31
|
+
align: 'left' | 'center';
|
|
32
|
+
surface: 'plain' | 'card';
|
|
33
|
+
};
|
|
@@ -8,38 +8,17 @@ import {
|
|
|
8
8
|
CTAButton,
|
|
9
9
|
EyebrowText,
|
|
10
10
|
Heading,
|
|
11
|
+
imageSrcsetField,
|
|
11
12
|
maskStyle,
|
|
13
|
+
resolveImageSrcset,
|
|
12
14
|
Section,
|
|
15
|
+
SIZES_HALF,
|
|
16
|
+
srcsetAttrs,
|
|
13
17
|
} from './_shared';
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
subtitle: string;
|
|
19
|
-
logoUrl: string;
|
|
20
|
-
logoAlt: string;
|
|
21
|
-
imageUrl: string;
|
|
22
|
-
imageAlt: string;
|
|
23
|
-
/**
|
|
24
|
-
* Organic mask (SVG url, from the tenant's content) applied to the side
|
|
25
|
-
* image in the right/left layouts. Empty = the standard rounded media.
|
|
26
|
-
*/
|
|
27
|
-
imageMaskUrl?: string;
|
|
28
|
-
/**
|
|
29
|
-
* "native" renders supplied transparent artwork exactly as-is — no mask, no
|
|
30
|
-
* crop — for a photo already cut to its final organic shape. Preferred over
|
|
31
|
-
* `imageMaskUrl`, which re-derives the silhouette from a hand-kept SVG.
|
|
32
|
-
*/
|
|
33
|
-
imageShape?: 'rounded' | 'native';
|
|
34
|
-
imagePosition: 'background' | 'right' | 'left';
|
|
35
|
-
primaryLabel: string;
|
|
36
|
-
primaryHref: string;
|
|
37
|
-
secondaryLabel: string;
|
|
38
|
-
secondaryHref: string;
|
|
39
|
-
overlay: boolean;
|
|
40
|
-
align: 'left' | 'center';
|
|
41
|
-
surface: 'plain' | 'card';
|
|
42
|
-
};
|
|
19
|
+
import type { HeroWidgetProps } from './hero-widget-props';
|
|
20
|
+
|
|
21
|
+
export type { HeroWidgetProps } from './hero-widget-props';
|
|
43
22
|
|
|
44
23
|
// Exported so CenteredHeroWidget can render through it without smuggling
|
|
45
24
|
// Puck's ComponentConfig.render signature past the type system.
|
|
@@ -51,6 +30,7 @@ export function renderHero({
|
|
|
51
30
|
logoAlt,
|
|
52
31
|
imageUrl,
|
|
53
32
|
imageAlt,
|
|
33
|
+
imageSrcset = '',
|
|
54
34
|
imageMaskUrl = '',
|
|
55
35
|
imageShape = 'rounded',
|
|
56
36
|
imagePosition,
|
|
@@ -120,6 +100,7 @@ export function renderHero({
|
|
|
120
100
|
<BackgroundMedia
|
|
121
101
|
imageUrl={imageUrl}
|
|
122
102
|
imageAlt={imageAlt}
|
|
103
|
+
imageSrcset={imageSrcset}
|
|
123
104
|
overlay={overlay ? 'dark' : 'none'}
|
|
124
105
|
fallback="gradient-mesh"
|
|
125
106
|
objectPosition="var(--pb-hero-image-position, 50% 50%)"
|
|
@@ -189,6 +170,7 @@ export function renderHero({
|
|
|
189
170
|
// Figma's box width. No mask, no crop — the file IS the shape.
|
|
190
171
|
<img
|
|
191
172
|
src={imageUrl}
|
|
173
|
+
{...srcsetAttrs(imageSrcset, SIZES_HALF)}
|
|
192
174
|
alt={imageAlt || title}
|
|
193
175
|
loading="lazy"
|
|
194
176
|
className="h-auto w-full max-w-[var(--pb-hero-media-max-w,none)]"
|
|
@@ -199,6 +181,7 @@ export function renderHero({
|
|
|
199
181
|
// unset, the image keeps its previous free-flowing size.
|
|
200
182
|
<img
|
|
201
183
|
src={imageUrl}
|
|
184
|
+
{...srcsetAttrs(imageSrcset, SIZES_HALF)}
|
|
202
185
|
alt={imageAlt || title}
|
|
203
186
|
loading="lazy"
|
|
204
187
|
className="h-auto w-full max-w-[var(--pb-hero-media-max-w,none)] object-cover"
|
|
@@ -208,7 +191,13 @@ export function renderHero({
|
|
|
208
191
|
}}
|
|
209
192
|
/>
|
|
210
193
|
) : (
|
|
211
|
-
<ContentMedia
|
|
194
|
+
<ContentMedia
|
|
195
|
+
imageUrl={imageUrl}
|
|
196
|
+
imageAlt={imageAlt}
|
|
197
|
+
imageSrcset={imageSrcset}
|
|
198
|
+
sizes={SIZES_HALF}
|
|
199
|
+
aspect="square"
|
|
200
|
+
/>
|
|
212
201
|
)}
|
|
213
202
|
</div>
|
|
214
203
|
</div>
|
|
@@ -226,6 +215,7 @@ export const HeroWidget: ComponentConfig<HeroWidgetProps> = {
|
|
|
226
215
|
logoAlt: { type: 'text', label: 'Logo alt text' },
|
|
227
216
|
imageUrl: createImageField(mediaLibraryAdapter, 'Image'),
|
|
228
217
|
imageAlt: { type: 'text', label: 'Image alt text' },
|
|
218
|
+
imageSrcset: imageSrcsetField,
|
|
229
219
|
imageMaskUrl: {
|
|
230
220
|
type: 'text',
|
|
231
221
|
label: 'Image mask URL (organic shape, side layouts — optional)',
|
|
@@ -277,6 +267,7 @@ export const HeroWidget: ComponentConfig<HeroWidgetProps> = {
|
|
|
277
267
|
logoAlt: '',
|
|
278
268
|
imageUrl: '',
|
|
279
269
|
imageAlt: '',
|
|
270
|
+
imageSrcset: '',
|
|
280
271
|
imageMaskUrl: '',
|
|
281
272
|
imageShape: 'rounded',
|
|
282
273
|
imagePosition: 'background',
|
|
@@ -288,5 +279,6 @@ export const HeroWidget: ComponentConfig<HeroWidgetProps> = {
|
|
|
288
279
|
align: 'center',
|
|
289
280
|
surface: 'plain',
|
|
290
281
|
},
|
|
282
|
+
resolveData: resolveImageSrcset,
|
|
291
283
|
render: renderHero,
|
|
292
284
|
};
|
|
@@ -5,6 +5,8 @@ import type { CSSProperties } from 'react';
|
|
|
5
5
|
export type MediaObjectWidgetProps = {
|
|
6
6
|
imageUrl: string;
|
|
7
7
|
imageAlt: string;
|
|
8
|
+
/** Variant srcset recorded at pick time (see _shared/image-srcset.ts). */
|
|
9
|
+
imageSrcset?: string;
|
|
8
10
|
imagePosition: 'left' | 'right';
|
|
9
11
|
heading: string;
|
|
10
12
|
body: string;
|
|
@@ -9,7 +9,7 @@ import { cn } from '../../utils/widgetUtils';
|
|
|
9
9
|
import { AccentText } from './_internal/accent-text';
|
|
10
10
|
import { LinkArrow } from './_internal/link-arrow';
|
|
11
11
|
import { RichTextBlock, renderRichText } from './_internal/rich-text';
|
|
12
|
-
import { CTAButton, eyebrowTextStyle, maskStyle } from './_shared';
|
|
12
|
+
import { CTAButton, eyebrowTextStyle, maskStyle, SIZES_HALF, srcsetAttrs } from './_shared';
|
|
13
13
|
|
|
14
14
|
import { GRID_IMAGE, GRID_TEXT, type MediaObjectWidgetProps } from './media-object-layout';
|
|
15
15
|
import { MediaObjectText } from './media-object-text';
|
|
@@ -19,6 +19,7 @@ export type { MediaObjectWidgetProps } from './media-object-layout';
|
|
|
19
19
|
export const MediaObjectWidgetRender = ({
|
|
20
20
|
imageUrl,
|
|
21
21
|
imageAlt,
|
|
22
|
+
imageSrcset = '',
|
|
22
23
|
imageMaskUrl = '',
|
|
23
24
|
imageShape = 'rounded',
|
|
24
25
|
imageTag = '',
|
|
@@ -86,6 +87,7 @@ export const MediaObjectWidgetRender = ({
|
|
|
86
87
|
const imageEl = imageUrl ? (
|
|
87
88
|
<img
|
|
88
89
|
src={imageUrl}
|
|
90
|
+
{...srcsetAttrs(imageSrcset, SIZES_HALF)}
|
|
89
91
|
alt={imageAlt || heading}
|
|
90
92
|
loading="lazy"
|
|
91
93
|
className={cn('w-full h-auto', !masked && !native && 'rounded-lg')}
|
|
@@ -196,6 +198,8 @@ export const MediaObjectWidgetRender = ({
|
|
|
196
198
|
{imageUrl && (
|
|
197
199
|
<img
|
|
198
200
|
src={imageUrl}
|
|
201
|
+
// Rendered 68px tall — the smallest variant is plenty.
|
|
202
|
+
{...srcsetAttrs(imageSrcset, '320px')}
|
|
199
203
|
alt={imageAlt || heading}
|
|
200
204
|
loading="lazy"
|
|
201
205
|
className="mt-8 h-[68px] w-auto max-w-full object-contain object-left"
|
|
@@ -5,6 +5,7 @@ import type { ComponentConfig } from '@puckeditor/core';
|
|
|
5
5
|
|
|
6
6
|
import { createImageField, mediaLibraryAdapter } from '../../fields';
|
|
7
7
|
|
|
8
|
+
import { imageSrcsetField, resolveImageSrcset } from './_shared';
|
|
8
9
|
import { type MediaObjectWidgetProps, MediaObjectWidgetRender } from './media-object-render';
|
|
9
10
|
|
|
10
11
|
export type * from './media-object-render';
|
|
@@ -14,6 +15,7 @@ export const MediaObjectWidget: ComponentConfig<MediaObjectWidgetProps> = {
|
|
|
14
15
|
fields: {
|
|
15
16
|
imageUrl: createImageField(mediaLibraryAdapter, 'Image'),
|
|
16
17
|
imageAlt: { type: 'text', label: 'Image alt text' },
|
|
18
|
+
imageSrcset: imageSrcsetField,
|
|
17
19
|
imageMaskUrl: {
|
|
18
20
|
type: 'text',
|
|
19
21
|
label: 'Image mask URL (organic shape — optional)',
|
|
@@ -145,6 +147,7 @@ export const MediaObjectWidget: ComponentConfig<MediaObjectWidgetProps> = {
|
|
|
145
147
|
defaultProps: {
|
|
146
148
|
imageUrl: '',
|
|
147
149
|
imageAlt: '',
|
|
150
|
+
imageSrcset: '',
|
|
148
151
|
imageMaskUrl: '',
|
|
149
152
|
imageShape: 'rounded',
|
|
150
153
|
imageTag: '',
|
|
@@ -172,5 +175,6 @@ export const MediaObjectWidget: ComponentConfig<MediaObjectWidgetProps> = {
|
|
|
172
175
|
surface: 'default',
|
|
173
176
|
surfaceColor: '',
|
|
174
177
|
},
|
|
178
|
+
resolveData: resolveImageSrcset,
|
|
175
179
|
render: (props) => <MediaObjectWidgetRender {...props} />,
|
|
176
180
|
};
|
{simple_module_pagebuilder-0.0.3 → simple_module_pagebuilder-0.0.4}/pagebuilder/endpoints/seo.py
RENAMED
|
@@ -42,7 +42,10 @@ async def sitemap(
|
|
|
42
42
|
if not settings.sitemap_enabled:
|
|
43
43
|
return Response(status_code=404)
|
|
44
44
|
|
|
45
|
-
pages
|
|
45
|
+
# Column-only query: at a few thousand pages, loading full entities
|
|
46
|
+
# (with both block-JSON columns) made every crawl a multi-second,
|
|
47
|
+
# tens-of-MB request (issue #11).
|
|
48
|
+
pages = await PagesService(db).list_sitemap_entries()
|
|
46
49
|
origin = _public_origin(request, settings)
|
|
47
50
|
prefix = settings.public_route_prefix
|
|
48
51
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Media file serving: root resolution, cache headers, integrity check.
|
|
2
|
+
|
|
3
|
+
Extracted from ``module.py`` (which sits near the size cap) and shared with
|
|
4
|
+
:class:`~pagebuilder.media_service.MediaService` so the mount that *serves*
|
|
5
|
+
files and the service that *writes* them can never disagree about where the
|
|
6
|
+
media library lives (issue #14).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from fastapi.staticfiles import StaticFiles
|
|
17
|
+
from starlette.exceptions import HTTPException
|
|
18
|
+
from starlette.responses import PlainTextResponse, Response
|
|
19
|
+
from starlette.types import Scope
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
24
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Same override + sentinels the hosting package uses to find the project
|
|
29
|
+
# root, so a host and this module always agree on the anchor. Duplicated
|
|
30
|
+
# rather than imported: modules depend on core, not hosting.
|
|
31
|
+
_ENV_PROJECT_ROOT = "SM_PROJECT_ROOT"
|
|
32
|
+
_PROJECT_ROOT_SENTINELS = ("pyproject.toml", ".env", "alembic.ini")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _project_root() -> Path:
|
|
36
|
+
override = os.environ.get(_ENV_PROJECT_ROOT)
|
|
37
|
+
if override:
|
|
38
|
+
return Path(override)
|
|
39
|
+
cwd = Path.cwd().resolve()
|
|
40
|
+
for candidate in (cwd, *cwd.parents):
|
|
41
|
+
if any((candidate / s).exists() for s in _PROJECT_ROOT_SENTINELS):
|
|
42
|
+
return candidate
|
|
43
|
+
return cwd
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def resolve_media_root(configured: Path | str) -> Path:
|
|
47
|
+
"""Anchor a relative ``media_root`` to the project root, not the cwd.
|
|
48
|
+
|
|
49
|
+
A cwd-relative default meant every process with a different working
|
|
50
|
+
directory read and wrote a *different* media directory while sharing one
|
|
51
|
+
database — rows silently outlived their files (issue #14). Absolute
|
|
52
|
+
paths pass through untouched.
|
|
53
|
+
"""
|
|
54
|
+
root = Path(configured)
|
|
55
|
+
if not root.is_absolute():
|
|
56
|
+
root = _project_root() / root
|
|
57
|
+
return root.resolve()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MediaFiles(StaticFiles):
|
|
61
|
+
"""StaticFiles with immutable caching and an honest 404.
|
|
62
|
+
|
|
63
|
+
Media filenames are content-addressed at upload (a fresh UUID hex per
|
|
64
|
+
file, variants included), so a URL never serves different bytes —
|
|
65
|
+
long-lived immutable caching is as safe here as for hashed Vite assets
|
|
66
|
+
(issue #13). Missing files answer with a plain-text 404 instead of
|
|
67
|
+
falling through to the HTML app shell, which hid broken images behind
|
|
68
|
+
``text/html`` responses (issue #14).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
_CACHE_CONTROL = "public, max-age=31536000, immutable"
|
|
72
|
+
|
|
73
|
+
async def get_response(self, path: str, scope: Scope) -> Response:
|
|
74
|
+
try:
|
|
75
|
+
response = await super().get_response(path, scope)
|
|
76
|
+
except HTTPException as exc:
|
|
77
|
+
if exc.status_code == 404:
|
|
78
|
+
return PlainTextResponse("Not Found", status_code=404)
|
|
79
|
+
raise
|
|
80
|
+
if response.status_code in (200, 304):
|
|
81
|
+
response.headers.setdefault("Cache-Control", self._CACHE_CONTROL)
|
|
82
|
+
return response
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def count_missing_media_files(session: AsyncSession, media_root: Path) -> int:
|
|
86
|
+
"""Rows in the media library whose file (or a variant) is gone from disk."""
|
|
87
|
+
from sqlmodel import select
|
|
88
|
+
|
|
89
|
+
from pagebuilder.models import MediaAsset
|
|
90
|
+
|
|
91
|
+
result = await session.execute(select(MediaAsset.filename, MediaAsset.variants))
|
|
92
|
+
missing = 0
|
|
93
|
+
for filename, variants in result.all():
|
|
94
|
+
names = [filename] + [
|
|
95
|
+
meta["filename"] for meta in (variants or {}).values() if meta.get("filename")
|
|
96
|
+
]
|
|
97
|
+
if any(not (media_root / name).is_file() for name in names):
|
|
98
|
+
missing += 1
|
|
99
|
+
return missing
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def warn_on_orphaned_media(
|
|
103
|
+
session_factory: Callable[[], AsyncSession], media_root: Path
|
|
104
|
+
) -> int:
|
|
105
|
+
"""Log a warning when media rows point at files that don't exist.
|
|
106
|
+
|
|
107
|
+
Runs once at startup. This drift is otherwise invisible until a visitor
|
|
108
|
+
sees broken images: the DB says the asset exists, the picker lists it,
|
|
109
|
+
and only the file request fails. Failures of the check itself (e.g. the
|
|
110
|
+
table doesn't exist yet on a fresh database) are logged at debug and
|
|
111
|
+
never block startup.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
async with session_factory() as session:
|
|
115
|
+
missing = await count_missing_media_files(session, media_root)
|
|
116
|
+
except Exception: # pragma: no cover - defensive: never break boot
|
|
117
|
+
logger.debug("pagebuilder.media integrity check skipped", exc_info=True)
|
|
118
|
+
return 0
|
|
119
|
+
if missing:
|
|
120
|
+
logger.warning(
|
|
121
|
+
"pagebuilder.media: %d media row(s) have no file under %s — "
|
|
122
|
+
"uploads from a different working directory or a missing volume. "
|
|
123
|
+
"Re-upload the assets or point SM_PAGEBUILDER_MEDIA_ROOT at the "
|
|
124
|
+
"directory that holds them.",
|
|
125
|
+
missing,
|
|
126
|
+
media_root,
|
|
127
|
+
)
|
|
128
|
+
return missing
|