# Upgrading from v3.x to v4.x

This release unifies the JavaScript architecture of the page builder. `ContentBuilder` (previously a separate class that `ContentBox` wrapped) and `ContentBox` (previously a jQuery plugin) have been collapsed into a single vanilla-JS `Editor` class. jQuery is no longer required in the main editor.

Everything that worked in v3.x still works, **but some names and call sites have changed**. This guide lists the breaking changes and how to migrate.

## Summary table

| Area | v3.x | v4.x | Migration |
|------|------|------|-----------|
| Initialisation | `jQuery('.is-wrapper').contentbox({...opts})` | `initEditor(element, {...opts})` | Replace jQuery selector + `.contentbox(...)` with the vanilla `initEditor()` helper. |
| Editor instance lookup | `jQuery('.is-wrapper').data('contentbox')` or `element._contentbox` | `element._contentbox` (still works) or `window.editor` | The global is now `window.editor` (was `window._cb`). `element._contentbox` still points at the editor instance. |
| Global reference | `window._cb` | `window.editor` | Rename all `window._cb` → `window.editor`. |
| Parent frame reference from iframes | `parent._cb.foo()` | `parent.editor.foo()` | Rename all `parent._cb.xxx` → `parent.editor.xxx`. |
| RTE plugin button registration | `_cb.addButton(pluginName, html, selector, callback)` | `editor.addPluginButton(pluginName, html, selector, callback)` | Rename `addButton` → `addPluginButton` for the 4-argument plugin form. The 1-argument `addButton(btn)` API (sidebar buttons) is unchanged. |
| Element toolbar plugin button | `_cb.addButton2(...)` | `editor.addPluginButton2(...)` | Rename `addButton2` → `addPluginButton2`. |
| HTML source viewer (sidebar) | `contentbox.viewHtml()` | `editor.viewHtmlSidebar()` | Any caller that relied on the ContentBox `viewHtml()` (syntax-highlighted sidebar flow) should use `viewHtmlSidebar()` now. |
| HTML source viewer (RTE modal) | `_cb.viewHtml(area)` (ContentBuilder) | `editor.viewHtml(area)` (unchanged behaviour) | Callers of the RTE/contentbuilder `viewHtml(area)` form are unchanged. |
| jQuery plugin bridge | `jQuery.fn.contentbox`, `jQuery.contentbox` | _(removed)_ | The jQuery wrapper has been deleted. Use `new Editor(element, opts)` or `initEditor(element, opts)` from vanilla JS. |
| jQuery in main editor bundle | required | not required | The editor bundle no longer depends on jQuery. Iframe utility pages (snippets, designs, symbols) still include jQuery because their own code uses it. |
| `this.builder` convention in sub-classes | `UndoRedo`, `Util`, `Preferences`, `HtmlUtil`, `ElementPanelCss`, `OpenAI` all stored parent as `this.builder` | All now use `this.editor` | This only affects custom sub-classes extending Bongo internals. Replace `this.builder.xxx` with `this.editor.xxx`. |
| Extracted sub-classes | inline in `editor.js` | `contentbuilder/sections/{SectionManager, SectionSettings, BoxEditor}.js`, `contentbuilder/ui/{ModalTemplates, EditBoxTemplates, SidebarTemplates, LinkCleaner}.js`, `contentbuilder/plugins/PluginManager.js`, `contentbuilder/utils/ScriptLoader.js` | Direct imports of these paths need updating if you have custom integrations. |

## Blade template migration

v3.x template:

```blade
<script>
    jQuery('.is-wrapper').contentbox({
        apiPath: '{{ config('builder.api_path') }}',
        backendAssetPath: '{{ config('builder.backend_asset_path') }}',
        frontendAssetPath: '{{ config('builder.frontend_asset_path') }}',
        onChange: function () {
            document.querySelector('#inpHtml').value = jQuery('.is-wrapper').data('contentbox').html();
        }
    });
</script>
```

v4.x equivalent:

```blade
<script>
    let wrapper = document.querySelector('.is-wrapper');
    let editor = initEditor(wrapper, {
        apiPath: '{{ config('builder.api_path') }}',
        backendAssetPath: '{{ config('builder.backend_asset_path') }}',
        frontendAssetPath: '{{ config('builder.frontend_asset_path') }}',
        onChange: function () {
            document.querySelector('#inpHtml').value = editor.html();
        }
    });
</script>
```

The `cms/page`, `cms/post`, and `custom/project` packages have already been migrated.

## Custom plugin migration

v3.x plugin (`plugins/myplugin/plugin.js`):

```js
(function () {
    var html = '<button class="myplugin-button" title="My Plugin">X</button>';
    _cb.addButton('myplugin', html, '.myplugin-button', function () {
        // ...
    });
    _cb.addButton2('myplugin', html, '.myplugin-button', function () {
        // ...
    });
})();
```

v4.x equivalent:

```js
(function () {
    var html = '<button class="myplugin-button" title="My Plugin">X</button>';
    editor.addPluginButton('myplugin', html, '.myplugin-button', function () {
        // ...
    });
    editor.addPluginButton2('myplugin', html, '.myplugin-button', function () {
        // ...
    });
})();
```

Also replace any `_cb.xxx` with `editor.xxx`, and any `parent._cb.xxx` (from iframe plugins) with `parent.editor.xxx`.

## Iframe utility page migration

v3.x iframe plugin:

```js
parent._cb.getScope();
parent._cb.settings.emailMode;
parent._cb.saveForUndo();
parent._cb.opts.onChange();
```

v4.x equivalent:

```js
parent.editor.getScope();
parent.editor.settings.emailMode;
parent.editor.saveForUndo();
parent.editor.opts.onChange();
```

## `showModal` / `hideModal` signature change

The Editor class exposes a simplified single-argument form:

```js
editor.showModal(modal);
editor.hideModal(modal);
```

The original four-argument version (overlay persistence, cancel callback, animation flag) is still available on the utility object:

```js
editor.util.showModal(modal, overlayStay, cancelCallback, animated);
```

If you need the extended behaviour, call through `editor.util` directly.

## What does NOT change

- **The editor's configuration options** (`apiPath`, `backendAssetPath`, `frontendAssetPath`, `snippetCategories`, `largerImageHandler`, `coverImageHandler`, `onChange`, `onRender`, etc.) are identical.
- **Public methods** for getting / loading HTML: `editor.html()`, `editor.loadHtml(html)`, `editor.readHtml(el, bForView, multiple)`, `editor.fromViewToActual(html)`.
- **Undo / redo**: `editor.undo()`, `editor.redo()`, `editor.saveForUndo()`.
- **Section APIs**: `editSection`, `addSection`, `sectionUp/Down/Top/Bottom`, `sectionHeight`, `sectionCss`, `mainCss`.
- **Box APIs**: all `applyBoxBehavior`, `editBox`, `boxWidth`, `boxBgColor`, etc. (now delegated to `BoxEditor` but reachable via the same names on the editor).
- **Script loading helpers**: `editor.getScript(url, cb)`, `editor.getScripts([urls], cb)`, `editor.includeJs(file, cb)`, `editor.includeCss(file)`, `editor.applyStyle(id, css)`.
- **Factory methods**: `editor.colorpicker(onPick, defaultColor)`, `editor.gradientpicker()`, `editor.dropdown(element, opts)`.
- **Backward-compat aliases** that remain available: `editor.loadHTML(html)`, `editor.viewConfig()`, `editor.pickColor(onPick, color)`, `editor.LightenDarkenColor(col, amt)`.

## Removed / deprecated

- `jQuery.fn.contentbox` and `jQuery.contentbox` (constructor function). Use `initEditor()` or `new Editor()`.
- `window._cb` global. Use `window.editor`.
- `resources/backend/js/contentbuilder/contentbox/` directory (absorbed into `contentbuilder/ui/*.js`).
- `resources/backend/js/contentbuilder/contentbuilder.js` class (absorbed into `Editor`).
- `resources/backend/js/contentbuilder/index.js` barrel export (no longer needed).

## If you've vendored or extended internals

If you have forked / overridden any of these files, review their v4.x counterparts before upgrading:

- `SectionManager.js`, `SectionSettings.js`, `BoxEditor.js` — extracted from `editor.js`. They receive a `context` object instead of a jQuery plugin reference.
- `UndoRedo.js`, `Util.js`, `Preferences.js`, `HtmlUtil.js`, `ElementPanelCss.js`, `OpenAI.js` — constructor parameter renamed from `builder` to `editor`; `this.builder` → `this.editor` throughout.
- `PluginManager.js` — new location. Register plugin buttons via `editor.pluginManager.addButton(name, html, sel, cb)` directly, or the convenience wrapper `editor.addPluginButton(...)`.

## Troubleshooting

**"editor is not defined" in a plugin file** — the plugin file runs before `initEditor()` has been called. Make sure the plugin is loaded after the editor instance is created (PluginManager does this automatically for plugins registered in `editor.settings.plugins`).

**"parent.editor is undefined" in an iframe** — the iframe loaded before the parent created the editor. Make sure the parent page has completed `initEditor()` before opening iframes that depend on it.

**jQuery not defined in snippets/designs/symbols iframe** — these iframe pages still depend on jQuery. Keep the jQuery script tag in `resources/views/api/{snippets,designs,symbols}.blade.php`.
