Divi Integration#38
Conversation
📝 WalkthroughWalkthroughChangesThe plugin now detects Divi and supported builders, adds a Divi editor adapter with target persistence and picking, integrates the shared Interaction Library across editors, updates builder-specific controls and styles, and saves the active editor during interaction publishing. Divi editor integration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DiviVisualBuilder
participant DiviInteractionsEditor
participant DiviDataAPI
participant InteractionLibrary
participant InteractionPanel
DiviVisualBuilder->>DiviInteractionsEditor: load divi editor
DiviInteractionsEditor->>DiviDataAPI: read or persist module target
DiviDataAPI-->>DiviInteractionsEditor: return target data
DiviInteractionsEditor->>InteractionLibrary: open apply mode with selected target
InteractionLibrary->>InteractionPanel: apply interaction
InteractionPanel->>DiviInteractionsEditor: save current editor
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Pull request artifacts
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/editor/components/interaction-panel/index.js (1)
225-237: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUI can get stuck in "publishing" forever if
saveCurrentEditor()rejects or throws.There's no
.catchon this chain. If the active editor'ssaveEditor()(e.g. Divi's DOM-based implementation) throws synchronously or its promise rejects,.finally()'s state resets (setStatus('idle'),setIsDirty(false), and the optionalcallback) never run, leaving the panel permanently busy with no recovery short of a page reload.🛡️ Suggested fix
onChange( editedInteraction ).then( () => { - return Promise.resolve( saveCurrentEditor() ).finally( () => { + return Promise.resolve( saveCurrentEditor() ).catch( () => {} ).finally( () => { setStatus( 'idle' ) setIsDirty( false ) if ( callback ) { setTimeout( callback, 1 ) // Need a timeout here because re-publishing may be too fast. } } ) } )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/editor/components/interaction-panel/index.js` around lines 225 - 237, Update onPublishHandler so both synchronous throws and rejected promises from saveCurrentEditor() still reset publishing state. Wrap the saveCurrentEditor invocation in a promise-safe flow and ensure setStatus('idle'), setIsDirty(false), and the optional callback execute on success or failure, while preserving the existing callback timing and allowing the rejection to be handled appropriately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/editor/editors/divi.js`:
- Around line 270-330: Update registerSelectionTracking so it tracks the
specific document currently bound rather than relying only on isBound, allowing
bindListeners to reattach when getCanvasDocument() returns a new iframe
document. Replace the one-shot 10-second observer shutdown with a retry
mechanism or iframe load handling that still binds canvases loading after the
initial attempt, while preserving cleanup of listeners, observers, and timers in
the returned teardown function.
- Around line 119-131: Update saveEditor() to identify Divi’s save button using
a locale-independent attribute or selector instead of the English text label,
then return a promise that resolves only after Divi finishes persisting the page
following saveButton.click(). Preserve the existing no-button and
disabled-button early-return behavior.
---
Outside diff comments:
In `@src/editor/components/interaction-panel/index.js`:
- Around line 225-237: Update onPublishHandler so both synchronous throws and
rejected promises from saveCurrentEditor() still reset publishing state. Wrap
the saveCurrentEditor invocation in a promise-safe flow and ensure
setStatus('idle'), setIsDirty(false), and the optional callback execute on
success or failure, while preserving the existing callback timing and allowing
the rejection to be handled appropriately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ab2b8f4c-80c3-4199-a296-f76c9d6a2c5b
📒 Files selected for processing (8)
interactions.phpsrc/editor/components/interaction-panel/index.jssrc/editor/components/target-selector/index.jssrc/editor/editor.phpsrc/editor/editor.scsssrc/editor/editors/abstract.jssrc/editor/editors/divi.jssrc/editor/editors/index.js
| saveEditor() { | ||
| // Reuse Divi's own save button so the builder persists the current page | ||
| // after an interaction modifies module attributes such as target IDs. | ||
| const saveButton = Array.from( document.querySelectorAll( '.et-vb-page-bar-action-button' ) ) | ||
| .find( button => button.textContent?.trim() === 'Save' ) | ||
|
|
||
| if ( ! saveButton || saveButton.disabled ) { | ||
| return Promise.resolve() | ||
| } | ||
|
|
||
| saveButton.click() | ||
| return Promise.resolve() | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if a similar textContent-based matching pattern already exists elsewhere
# in the codebase (e.g. for Bricks/Elementor save flows) for consistency reference.
rg -n "textContent" src/editor/editors -C2
rg -n "saveEditor" src/editor/editors -C3
fd . src/editor/editorsRepository: gambitph/Interactions
Length of output: 1946
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant editor implementations and surrounding call sites.
sed -n '1,220p' src/editor/editors/divi.js | cat -n
printf '\n==== abstract.js ====\n'
sed -n '1,180p' src/editor/editors/abstract.js | cat -n
printf '\n==== index.js ====\n'
sed -n '1,120p' src/editor/editors/index.js | cat -n
printf '\n==== Divi-specific searches ====\n'
rg -n "et-vb-page-bar-action-button|Save|saveEditor|click\(\)|promise|await|mutation|event" src/editor -C2Repository: gambitph/Interactions
Length of output: 49115
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the save flow to see how saveCurrentEditor() is consumed.
rg -n "saveCurrentEditor\(" src -C3
printf '\n==== interaction-panel save flow ====\n'
sed -n '220,380p' src/editor/components/interaction-panel/index.js | cat -n
printf '\n==== use-on-post-save helper ====\n'
sed -n '1,140p' src/editor/use-on-post-save.js | cat -nRepository: gambitph/Interactions
Length of output: 9974
saveEditor() shouldn't depend on the English "Save" label or return before Divi finishes persisting. textContent?.trim() === 'Save' misses localized UIs, and click() is followed by an immediate Promise.resolve(), so saveCurrentEditor() can advance before Divi has actually saved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/editor/editors/divi.js` around lines 119 - 131, Update saveEditor() to
identify Divi’s save button using a locale-independent attribute or selector
instead of the English text label, then return a promise that resolves only
after Divi finishes persisting the page following saveButton.click(). Preserve
the existing no-button and disabled-button early-return behavior.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/editor/interaction-library/configure-modal.js`:
- Around line 90-100: Builder preset insertion is currently unavailable for
Bricks and Elementor because their adapters inherit the null implementation. In
src/editor/interaction-library/configure-modal.js lines 90-100, retain the
adapter-based insert flow but expose and use insert mode only when the active
adapter supports insertion; in src/editor/editors/bricks.js line 94, implement
insertLibraryPreset() to insert the preset and return the descriptor required by
resolveLibraryPresetTargets; apply the corresponding implementation in
src/editor/editors/elementor.js line 106.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 556696a0-6a53-41c7-be2b-2b7f10eec50b
📒 Files selected for processing (11)
interactions.phpsrc/editor/app.jssrc/editor/editors/abstract.jssrc/editor/editors/bricks.jssrc/editor/editors/divi.jssrc/editor/editors/elementor.jssrc/editor/editors/gutenberg.jssrc/editor/editors/index.jssrc/editor/interaction-library/configure-modal.jssrc/editor/interaction-library/index.jssrc/editor/interaction-library/select-modal.js
🚧 Files skipped from review as they are similar to previous changes (2)
- src/editor/editors/index.js
- src/editor/editors/divi.js
| // In insert mode, let the active editor adapter own how preset content | ||
| // is created so this modal no longer depends on Gutenberg block APIs. | ||
| if ( mode === 'insert' ) { | ||
| const block = parse( selectedPreset.serializedBlockExample ?? '' )[ 0 ] | ||
| if ( ! block ) { | ||
| const insertedContent = insertLibraryPreset( selectedPreset ) | ||
| if ( ! insertedContent ) { | ||
| return | ||
| } | ||
| dispatch( 'core/block-editor' ).insertBlocks( block ) | ||
|
|
||
| // If target mappings are provided, dynamically create target for each. | ||
| applyTargetMappings( interactionSetup, targetMappings, block, ) | ||
| resolveLibraryPresetTargets( interactionSetup, selectedPreset, insertedContent ) | ||
| } else if ( mode === 'apply' ) { | ||
| applyTargetMappings( interactionSetup, targetMappings, selectedTarget ) | ||
| resolveLibraryPresetTargets( interactionSetup, selectedPreset, selectedTarget ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Builder preset insertion is a no-op outside Gutenberg.
ConfigureModal now delegates insertion to the active adapter, but Bricks and Elementor inherit the abstract insertLibraryPreset() implementation, which returns null. Selecting Insert in either newly mounted library exits at Line 95 without adding content or an interaction.
src/editor/interaction-library/configure-modal.js#L90-L100: retain the adapter flow, but only expose/use insert mode where the active adapter implements insertion.src/editor/editors/bricks.js#L94-L94: implementinsertLibraryPreset()and return the inserted-content descriptor required for target mapping.src/editor/editors/elementor.js#L106-L106: implement the corresponding Elementor insertion adapter and descriptor.
📍 Affects 3 files
src/editor/interaction-library/configure-modal.js#L90-L100(this comment)src/editor/editors/bricks.js#L94-L94src/editor/editors/elementor.js#L106-L106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/editor/interaction-library/configure-modal.js` around lines 90 - 100,
Builder preset insertion is currently unavailable for Bricks and Elementor
because their adapters inherit the null implementation. In
src/editor/interaction-library/configure-modal.js lines 90-100, retain the
adapter-based insert flow but expose and use insert mode only when the active
adapter supports insertion; in src/editor/editors/bricks.js line 94, implement
insertLibraryPreset() to insert the preset and return the descriptor required by
resolveLibraryPresetTargets; apply the corresponding implementation in
src/editor/editors/elementor.js line 106.
Summary by CodeRabbit