# zip-ai/0.0.7/assets/js/tools/editor/scripts/handler.js

ZIP AI – AI Website Builder &amp; AI Agent (Beta), version 0.0.7. 143 lines.

- Page: https://pluginprobe.com/plugins/zip-ai/0.0.7/code/assets/js/tools/editor/scripts/handler.js
- Raw: https://pluginprobe.com/plugins/zip-ai/0.0.7/raw/assets/js/tools/editor/scripts/handler.js
- Modified: 2026-07-08T07:21:38+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/zip-ai/0.0.7/code/assets/js/tools/editor/scripts/handler.js#L10-L20`.

```javascript
/**
 * editor/get-scripts + editor/set-scripts — typed read/patch over a BLOCK's
 * `spectraCustomJS` attribute (the per-block JS store). `spectraCustomJS` in
 * post_content is the single source of truth for page behaviour — Spectra Pro's
 * BlockJsCompiler renders it once at wp_footer (spectra-blocks-pro #165),
 * superseding the removed per-page `_spectra_blocks_page_scripts` meta.
 *
 * SESSION-SCOPED BY DESIGN (the apply_change philosophy): reads come from
 * `core/block-editor` getBlockAttributes (the live editing session, incl.
 * unsaved edits) and writes go through updateBlockAttributes — the block's JS
 * updates in the session immediately, is DISCARDED if the user discards the
 * session, and persists only on Save. A REST write here would race the open
 * editor's copy and mutate before save — never do that.
 *
 * Target a block by `clientId` (default = the page root container, the first
 * top-level block — page-wide behaviour). The stored code is raw JS; the
 * renderer wraps it in an IIFE and resolves the `_current_block_` token to the
 * block's scope class.
 */
(function () {
    // Block-editor store access comes from the ONE shared source
    // (editor/shared/editor-shared-utils.js): window in the browser, require()
    // under jest — so the editor handlers can't drift.
    function sharedEditorUtils() {
        if (typeof window !== 'undefined' && window.zipwpEditorShared) return window.zipwpEditorShared;
        if (typeof require === 'function') {
            try { return require('../shared/editor-shared-utils.js'); } catch (e) { return null; }
        }
        return null;
    }
    function blockEditorSelect() { var u = sharedEditorUtils(); return u && u.blockEditorSelect ? u.blockEditorSelect() : null; }
    function blockEditorDispatch() { var u = sharedEditorUtils(); return u && u.blockEditorDispatch ? u.blockEditorDispatch() : null; }
    function rootClientId(sel) { var u = sharedEditorUtils(); return u && u.rootClientId ? u.rootClientId(sel) : null; }
    function currentPostId() { var u = sharedEditorUtils(); return u && u.currentPostId ? u.currentPostId() : null; }

    // The target block: an explicit clientId, else the page root container.
    function resolveClientId(sel, args) {
        var cid = args && typeof args.clientId === 'string' && args.clientId !== '' ? args.clientId : null;
        return cid || rootClientId(sel);
    }

    // A block's current spectraCustomJS ('' when unset or the block is gone).
    function customJsOf(sel, clientId) {
        var attrs = sel && sel.getBlockAttributes ? sel.getBlockAttributes(clientId) : null;
        return attrs && typeof attrs.spectraCustomJS === 'string' ? attrs.spectraCustomJS : '';
    }

    function handleGetScripts(args) {
        var sel = blockEditorSelect();
        if (!sel || !sel.getBlockAttributes) {
            return { success: false, error: 'editor_unavailable: core/block-editor store not present (is the block editor open?)' };
        }
        var clientId = resolveClientId(sel, args);
        if (!clientId) {
            return { success: false, error: 'no_block: the page has no blocks to read JS from' };
        }
        return {
            success: true,
            data: {
                post_id: currentPostId(),
                client_id: clientId,
                code: customJsOf(sel, clientId),
            },
        };
    }

    // Throws on code the block renderer would reject — typed and loud beats a
    // silent drop. `<script>` tags never belong in the store (it wraps the raw JS).
    function validateCode(code) {
        if (typeof code !== 'string' || code === '') {
            throw new Error('invalid_input: code is required (the raw JS source, no <script> tags)');
        }
        if (/<\/?script/i.test(code)) {
            throw new Error('invalid_input: code must be the raw JS source only, with no <script> tags (the store wraps it)');
        }
    }

    // `code` REPLACES the block's spectraCustomJS; `append: true` adds to the
    // existing JS instead (read first with editor/get-scripts).
    function handleSetScripts(args) {
        var sel = blockEditorSelect();
        var dis = blockEditorDispatch();
        if (!sel || !sel.getBlockAttributes || !dis || !dis.updateBlockAttributes) {
            return { success: false, error: 'editor_unavailable: core/block-editor store not present (is the block editor open?)' };
        }

        try { validateCode(args ? args.code : undefined); } catch (e) {
            return { success: false, error: String(e && e.message ? e.message : e) };
        }

        var clientId = resolveClientId(sel, args);
        if (!clientId) {
            return { success: false, error: 'no_block: the page has no blocks to attach JS to' };
        }
        var attrs = sel.getBlockAttributes(clientId);
        if (!attrs) {
            return { success: false, error: 'unknown_block: no block with clientId ' + clientId + ' in the live tree (read the outline / get-context first)' };
        }

        var existing = typeof attrs.spectraCustomJS === 'string' ? attrs.spectraCustomJS : '';
        var next = (args.append === true && existing !== '') ? existing + '\n' + args.code : args.code;
        dis.updateBlockAttributes(clientId, { spectraCustomJS: next });

        return {
            success: true,
            data: {
                client_id: clientId,
                code: next,
                note: 'Session-scoped: the block\'s JS updates now; persists when the user saves the page.',
            },
        };
    }

    // Register once the bridge is ready (same retry pattern as get-context /
    // apply-change). The react-manager glob auto-enqueues this file.
    function initHandler() {
        if (window.zipwpMcp && window.zipwpMcp.registerTool) {
            window.zipwpMcp.registerTool(
                'editor/get-scripts',
                async function (args) { return handleGetScripts(args); },
                { previewMode: 'client' }
            );
            window.zipwpMcp.registerTool(
                'editor/set-scripts',
                async function (args) { return handleSetScripts(args); },
                { previewMode: 'client' }
            );
        } else {
            setTimeout(initHandler, 100);
        }
    }

    initHandler();

    // Test-only surface (Node/CommonJS) — inert in the browser bundle.
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = {
            handleGetScripts: handleGetScripts,
            handleSetScripts: handleSetScripts,
        };
    }
})();

```
