Add Visual Editing to Live Preview pane#26264
Add Visual Editing to Live Preview pane#26264bryantgillespie wants to merge 3 commits intofeat/aifrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds visual editing capability to the live preview pane, enabling content editors to edit directly within the preview without navigating to the visual module. The implementation includes a two-layer validation system (prerequisites and same-origin checks), a full-width toggle for the preview panel, and enhanced UX for managing editable overlays.
- Introduced
useVisualEditingcomposable for centralized prerequisite validation - Added same-origin validation in the live preview component
- Implemented full-width toggle with persistent state via localStorage
- Enhanced editing layer to emit save events for parent component refresh
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/views/private/components/live-preview.vue | Added visual editing integration with two-layer validation, editable elements toggle button, and editing layer component |
| app/src/utils/normalize-url.ts | New utility to normalize and validate URLs against the window location |
| app/src/styles/_base.scss | Moved iframe pointer-events CSS rule to global scope for better maintainability |
| app/src/modules/visual/components/editing-layer.vue | Added saved event emission to notify parent components of save actions |
| app/src/modules/content/routes/preview.vue | Integrated visual editing prerequisites and configuration for popup preview mode |
| app/src/modules/content/routes/item.vue | Added full-width toggle, visual editing integration, and save event handler for the split panel preview |
| app/src/composables/use-visual-editing.ts | New composable for checking visual editing prerequisites including module status, URL configuration, and item state |
| /** | ||
| * Normalizes a URL string by resolving it against the current window location. | ||
| * Returns null if the URL is invalid or empty. | ||
| */ | ||
| export function normalizeUrl(url: string | null): string | null { | ||
| if (!url) return null; | ||
|
|
||
| try { | ||
| return new URL(url, window.location.href).href; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing test coverage for the normalizeUrl utility function. Since the app/src/utils directory contains comprehensive test files for most utility functions (e.g., add-query-to-path.test.ts, format-filesize.test.ts), this new utility should also have test coverage to ensure it correctly handles edge cases like:
- Invalid URLs
- Relative URLs
- Absolute URLs
- Empty/null inputs
- URLs with special characters
| import { computed, unref, type MaybeRef } from 'vue'; | ||
| import { MODULE_BAR_DEFAULT } from '@/constants'; | ||
| import { useSettingsStore } from '@/stores/settings'; | ||
| import { normalizeUrl } from '@/utils/normalize-url'; | ||
|
|
||
| interface UseVisualEditingOptions { | ||
| /** Preview URL to check (will be normalized internally) */ | ||
| previewUrl: MaybeRef<string | null>; | ||
| /** Whether this is a new item - visual editing is disabled for new items. Defaults to false. */ | ||
| isNew?: MaybeRef<boolean>; | ||
| } | ||
|
|
||
| /** | ||
| * Handles visual editing prerequisites check. | ||
| * Returns whether visual editing can be enabled and the allowed URLs for sameOrigin validation. | ||
| * | ||
| * Note: This checks prerequisites only. The live-preview component does the final | ||
| * sameOrigin validation against the currently displayed URL. | ||
| */ | ||
| export function useVisualEditing({ previewUrl, isNew = false }: UseVisualEditingOptions) { | ||
| const settingsStore = useSettingsStore(); | ||
| const moduleBar = computed(() => settingsStore.settings?.module_bar ?? MODULE_BAR_DEFAULT); | ||
|
|
||
| const visualEditorUrls = computed( | ||
| () => settingsStore.settings?.visual_editor_urls?.map(({ url }) => url).filter(Boolean) ?? [], | ||
| ); | ||
|
|
||
| const visualModuleEnabled = computed(() => | ||
| moduleBar.value.some((part) => part.type === 'module' && part.id === 'visual' && part.enabled), | ||
| ); | ||
|
|
||
| const normalizedPreviewUrl = computed(() => normalizeUrl(unref(previewUrl))); | ||
|
|
||
| /** Prerequisites check - live-preview does the final sameOrigin validation */ | ||
| const visualEditingEnabled = computed( | ||
| () => | ||
| !!normalizedPreviewUrl.value && | ||
| !unref(isNew) && | ||
| visualModuleEnabled.value && | ||
| visualEditorUrls.value.length > 0, | ||
| ); | ||
|
|
||
| return { visualEditingEnabled, visualEditorUrls }; | ||
| } |
There was a problem hiding this comment.
Missing test coverage for the useVisualEditing composable. Since the app/src/composables directory contains comprehensive test files for composables (e.g., use-event-listener.test.ts, use-local-storage.test.ts), this new composable should have test coverage to verify:
- Correct handling of prerequisites (module enabled, URLs configured, valid item state)
- Behavior with new vs. existing items
- URL normalization integration
- Reactive updates when dependencies change
| * Two-layer visual editing check: | ||
| * 1. Parent checks prerequisites (module enabled, URLs configured, valid item state) | ||
| * 2. Child checks if the *currently displayed* URL (frameSrc) passes sameOrigin | ||
| * | ||
| * We use frameSrc here because the user may have selected a different URL from the dropdown |
There was a problem hiding this comment.
The comment describes a "two-layer visual editing check" but the logic only performs a single-layer check at this location. The parent's prerequisite check happens in useVisualEditing, but this comment implies both checks happen here. Consider clarifying the comment to reflect that this is performing the second layer (sameOrigin validation) based on the first layer's result passed via the canEnableVisualEditing prop.
| * Two-layer visual editing check: | |
| * 1. Parent checks prerequisites (module enabled, URLs configured, valid item state) | |
| * 2. Child checks if the *currently displayed* URL (frameSrc) passes sameOrigin | |
| * | |
| * We use frameSrc here because the user may have selected a different URL from the dropdown | |
| * Visual editing enablement (second-layer check): | |
| * This computed property only performs the second layer of the visual editing check: | |
| * - It checks if the *currently displayed* URL (frameSrc) passes sameOrigin validation | |
| * The first layer (prerequisite check: module enabled, URLs configured, valid item state) | |
| * is handled by the parent and passed via the `canEnableVisualEditing` prop. | |
| * We use frameSrc here because the user may have selected a different URL from the dropdown. |
visual-editing-preview-pane.mp4
Adds visual editing capability to the live preview pane. This is a nice improvement for content editors where visual editing is enabled because it uses the live preview URLs to render the visual editor on the same page without having to navigate to the module. No context switching.
This pull request introduces a robust visual editing integration for live previews, improving the user experience for editing content in-place. The changes add a two-layer validation for visual editing (prerequisites and same-origin checks), enable toggling of a full-width preview mode, and streamline the handling of editable overlays and their state. Additionally, code organization and UX are improved for both regular and popup preview modes.
Key changes include:
Visual Editing Integration & Validation:
useVisualEditingto centralize the logic for determining if visual editing can be enabled, including checks for module activation, URL configuration, and item state (use-visual-editing.ts).live-preview.vueto accept new props for visual editing capability and allowed URLs, and to perform a same-origin check on the currently displayed URL before enabling editing. [1] [2] [3]UI/UX Enhancements:
Communication & State Management:
editing-layer.vuecomponent to emit asavedevent after a successful save, allowing parent components to refresh data as needed. [1] [2] [3] [4] [5]Code Organization & Utilities:
normalizeUrlto standardize preview URLs and prevent errors from malformed inputs.These changes collectively make visual editing more robust, user-friendly, and maintainable.