Redactor 4 Vendor Customizations

This document tracks all customizations made to the Redactor 4 vendor files.
Review and re-apply these when upgrading Redactor.

Vendor Location: client/vendor/redactor4/
Current Version: 4.5.3 (with patches)
Last Updated: 2026-01-26
Patches By: Roman Mateja-Cabello (original), updated by AI assistant


Customization 1: Source Mode Scroll Jump Fix

File: redactor.js
Location: SourceModule._setHeight() (around line 13009)
Type: Bug fix
Status: Applied in 4.5.3

Problem

When switching to source mode, the original code sets height: auto which causes
the page to scroll/jump unexpectedly.

Solution

Clone the element to measure height without affecting the visible element,
then restore scroll position.

Original Code

_setHeight() {
    this.$source.get().style.height = 'auto';
    this.$source.get().style.height = `${this.$source.get().scrollHeight}px`;
}

Patched Code

_setHeight() {
    const el = this.$source.get();

    // Get current scroll position
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

    // Create a clone to measure the natural height without affecting the page
    // This avoids the flash caused by setting height='auto' on the real element
    const clone = el.cloneNode(true);
    clone.style.cssText = `
        position: absolute;
        visibility: hidden;
        height: auto;
        width: ${el.offsetWidth}px;
        overflow: hidden;
    `;
    el.parentNode.appendChild(clone);
    const newHeight = clone.scrollHeight;
    clone.remove();

    // Only update if height needs to change
    const currentHeight = el.offsetHeight;
    if (newHeight !== currentHeight) {
        el.style.height = `${newHeight}px`;
    }

    // Restore scroll position immediately
    window.scrollTo(0, scrollTop);
}

Upgrade Check

Search for _setHeight() in the new version's SourceModule. If still using
height: auto, this patch is still needed.


Customization 2: Block Focus Undefined Guard

File: redactor.js
Location: BlockModule focus handling (around line 21525)
Type: Bug fix
Status: Applied in 4.5.3

Problem

After reassigning this.instance = this.instance.getParent(), the instance
could be undefined, causing errors.

Solution

Add a guard clause after the reassignment.

Patched Code

// Guard against undefined instance after reassignment (bug fix)
if (!this.instance) return;

Upgrade Check

Search for this.instance.getParent() and verify proper null handling exists.


Customization 3: Image Inline-Block Option

File: redactor.js
Location: ImageManager and block.image (multiple locations)
Type: Feature addition (WarmlyYours specific)
Status: Custom feature, always needs re-application

Purpose

Adds an "Inline block (for centering)" checkbox to the image properties dialog.
This allows users to set display: inline-block on images for centering purposes.

Changes Required

  1. ImageManager form items (~line 23170):
inlineBlock: { type: 'checkbox', text: 'Inline block (for centering)', observer: 'image.observeInlineBlock', auto: true },
  1. ImageManager observer (~line 23262):
observeInlineBlock(obj) {
    // Always show the inline-block option (WY custom feature)
    return obj;
}
  1. Block image props (~line 25623):
inlineBlock: { getter: 'getInlineBlock', setter: 'setInlineBlock' },
  1. Block image methods (~line 25684):
getInlineBlock() {
    const display = this.$image.css('display');
    return display === 'inline-block';
},
setInlineBlock(value) {
    if (value) {
        this.$image.css('display', 'inline-block');
    } else {
        this.$image.css('display', '');
        if (this.$image.attr('style') === '') {
            this.$image.removeAttr('style');
        }
    }
},

Customization 4: Email Container Centering Fix

File: plugins/email/email.js
Location: _buildMainEmail() (around line 1097)
Type: Bug fix / Email compatibility
Status: Applied in 4.5.3

Problem

The email plugin's _buildMainEmail() creates the 600px container table but doesn't
add centering attributes. While the parent <td> has align="center", some email
clients (especially Apple Mail) don't properly center nested tables without explicit
centering on the table itself.

Solution

Add align="center" attribute and margin: 0 auto CSS to the email-container table.

Original Code

const $container = this.dom('<table>');
$container.addClass('email-container');
$container.attr({
    'width': widthAttr,
    'cellpadding': '0',
    'cellspacing': '0',
    'role': 'presentation'
});

Patched Code

const $container = this.dom('<table>');
$container.addClass('email-container');
$container.css({ 'margin': '0 auto' });
$container.attr({
    'width': widthAttr,
    'cellpadding': '0',
    'cellspacing': '0',
    'align': 'center',
    'role': 'presentation'
});

Upgrade Check

Search for _buildMainEmail in the email plugin. If the container table doesn't have
align: 'center' and margin: 0 auto, this patch is still needed.


Upgrade Procedure

1. Backup current vendor files

cp -r client/vendor/redactor4 client/vendor/redactor4-backup-$(date +%Y%m%d)

2. Copy new version files

# Core Redactor
cp extras/redactor/redactor-X-X-X/redactor.js client/vendor/redactor4/
cp extras/redactor/redactor-X-X-X/redactor.css client/vendor/redactor4/
cp extras/redactor/redactor-X-X-X/redactor.min.js client/vendor/redactor4/
cp extras/redactor/redactor-X-X-X/redactor.min.css client/vendor/redactor4/

# Plugins (copy folders as needed)
cp -r extras/redactor/redactor-X-X-X/plugins/* client/vendor/redactor4/plugins/

# Updated email plugin (if separate)
cp extras/redactor/email/* client/vendor/redactor4/plugins/email/

# Updated AI plugin (if separate)
cp extras/redactor/ai/* client/vendor/redactor4/plugins/ai/

3. Re-apply customizations

For each customization above:

  1. Check if the issue is fixed in the new version
  2. If not fixed, apply the patch manually
  3. Update line numbers in this document

4. Test checklist

  • Source mode toggle (no scroll jump)
  • Image inline-block option works
  • Email templates display correctly in editor
  • Email templates render correctly when sent
  • Blog editor works with all plugins (AI, FAQ, Video, Snippets)
  • Image insertion works (WY Image plugin)
  • Video insertion works (WY Video plugin)
  • No console errors

5. Update this document

  • New version number
  • Updated line numbers for patches
  • Any new customizations added

Future Consideration: Extractable Patches

Scroll Jump Fix (Could be runtime-patched)

This fix modifies an internal method and could potentially be applied at runtime
after Redactor loads. This would avoid modifying vendor files:

// Hypothetical runtime patch in redactor4.js
// Note: Requires understanding Redactor's module structure

// After Redactor is loaded, patch the SourceModule
const originalSetHeight = Redactor.modules?.Source?.prototype?._setHeight;
if (originalSetHeight) {
  Redactor.modules.Source.prototype._setHeight = function() {
    const el = this.$source.get();
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    
    const clone = el.cloneNode(true);
    clone.style.cssText = `position:absolute;visibility:hidden;height:auto;width:${el.offsetWidth}px;overflow:hidden`;
    el.parentNode.appendChild(clone);
    const newHeight = clone.scrollHeight;
    clone.remove();
    
    if (newHeight !== el.offsetHeight) {
      el.style.height = `${newHeight}px`;
    }
    window.scrollTo(0, scrollTop);
  };
}

Caveat: Redactor's internal structure may change between versions, making this
fragile. Direct patching is currently more reliable.

Inline-Block Feature (Could be a plugin)

The inline-block checkbox feature could potentially be extracted into a standalone
Redactor plugin (wyinlineblock.js) that extends the image block's behavior.
This would require:

  1. Understanding Redactor's plugin API for extending existing blocks
  2. Creating a proper plugin structure
  3. Testing compatibility with each Redactor version

This is a larger effort but would make upgrades trivial.


Related Files

  • Redactor config: client/js/crm/editors/redactor4.js
  • Stimulus controller: app/javascript/controllers/redactor4_init_controller.js
  • WY Image plugin: client/js/common/wyimage4.js
  • WY Video plugin: client/js/common/wyvideo4.js
  • WY FAQ plugin: client/js/common/wyfaq4.js
  • WY Embed plugin: client/js/common/wyembed4.js
  • WY Email Blocks plugin: client/js/common/wyemailblocks.js