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.6.2 (with patches)
Last Updated: 2026-07-25
Patches By: Roman Mateja-Cabello (original), updated by AI assistant
Customization 1: Source Mode Scroll Jump Fix
Section titled “Customization 1: Source Mode Scroll Jump Fix”File: redactor.js
Location: SourceModule._setHeight() and _adjustHeight()
Type: Bug fix
Status: Re-applied 2026-07-25 (4.6.2 upgrade; upstream still unpatched)
Problem
Section titled “Problem”In Redactor source (HTML/CodeMirror) mode, _setHeight() (called on every CM ‘change’)
and _adjustHeight() did height = 'auto' then px on the backing <textarea>.
Even though CM hides the textarea and sizes itself, the style mutation + any
ancillary scroll/offset reads were causing the main viewport to jump to the top
on every keystroke, followed by a soft scroll back to the caret. Extremely
disruptive when editing long blog posts.
This also happened on open (adjust) and was exacerbated after vendor upgrades that dropped prior patches.
Solution
Section titled “Solution”- Guard
_setHeight(): when CodeMirror is active, do nothing (CM owns the viewport/scrolling for source). - In
_adjustHeight()(open time): measure required height with an off-DOM clone so the live element is never temporarily set to ‘auto’. - Never force
window.scrollTo(0, ...)restores on the hot path.
Relevant Code (current patched)
Section titled “Relevant Code (current patched)”_adjustHeight(editor) { const minHeight = this._getMinHeight(); const el = this.$source.get(); // Measure without mutating the live element (prevents scroll jump on open) 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 contentHeight = clone.scrollHeight; clone.remove(); const height = Math.max(contentHeight, minHeight);
el.style.height = height + 'px'; return Math.max(editor.height(), minHeight);}
_setHeight() { const cm = this.app.codemirror && this.app.codemirror.cm; if (cm) { // CodeMirror manages its own viewport and scrolling for the source view. return; } this.$source.get().style.height = 'auto'; this.$source.get().style.height = `${this.$source.get().scrollHeight}px`;}Upgrade Check
Section titled “Upgrade Check”After any Redactor upgrade, ensure:
_setHeight()short-circuits whenthis.app.codemirror.cmexists._adjustHeight()uses a hidden clone for measurement rather than mutating the live.rx-sourcetextarea.
Customization 2: Block Focus Undefined Guard
Section titled “Customization 2: Block Focus Undefined Guard”File: redactor.js
Location: BlockModule focus handling (around line 21525)
Type: Bug fix
Status: OBSOLETE — upstream since 4.6.x; 4.6.2 adds its own if (!this.instance?.getBlock) return; guard. No re-application needed.
Problem
Section titled “Problem”After reassigning this.instance = this.instance.getParent(), the instance
could be undefined, causing errors.
Solution
Section titled “Solution”Add a guard clause after the reassignment.
Patched Code
Section titled “Patched Code”// Guard against undefined instance after reassignment (bug fix)if (!this.instance) return;Upgrade Check
Section titled “Upgrade Check”Search for this.instance.getParent() and verify proper null handling exists.
Customization 3: Image Inline-Block Option
Section titled “Customization 3: Image Inline-Block Option”File: redactor.js
Location: ImageManager and block.image (multiple locations)
Type: Feature addition (WarmlyYours specific)
Status: DROPPED since the 4.6.0 upgrade (commit 6311f859d3) — not re-applied in 4.6.1/4.6.2, no user reports since. Resurrect from client/vendor/redactor4-backup-20260126/ only if requested.
Purpose
Section titled “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
Section titled “Changes Required”- ImageManager form items (~line 23170):
inlineBlock: { type: 'checkbox', text: 'Inline block (for centering)', observer: 'image.observeInlineBlock', auto: true },- ImageManager observer (~line 23262):
observeInlineBlock(obj) { // Always show the inline-block option (WY custom feature) return obj;}- Block image props (~line 25623):
inlineBlock: { getter: 'getInlineBlock', setter: 'setInlineBlock' },- 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
Section titled “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
Section titled “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
Section titled “Solution”Add align="center" attribute and margin: 0 auto CSS to the email-container table.
Original Code
Section titled “Original Code”const $container = this.dom('<table>');$container.addClass('email-container');$container.attr({ 'width': widthAttr, 'cellpadding': '0', 'cellspacing': '0', 'role': 'presentation'});Patched Code
Section titled “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
Section titled “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
Section titled “Upgrade Procedure”1. Backup current vendor files
Section titled “1. Backup current vendor files”cp -r client/vendor/redactor4 client/vendor/redactor4-backup-$(date +%Y%m%d)2. Copy new version files
Section titled “2. Copy new version files”# Core Redactorcp 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
Section titled “3. Re-apply customizations”For each customization above:
- Check if the issue is fixed in the new version
- If not fixed, apply the patch manually
- Update line numbers in this document
4. Test checklist
Section titled “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
Section titled “5. Update this document”- New version number
- Updated line numbers for patches
- Any new customizations added
Future Consideration: Extractable Patches
Section titled “Future Consideration: Extractable Patches”Scroll Jump Fix (Could be runtime-patched)
Section titled “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 SourceModuleconst 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)
Section titled “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:
- Understanding Redactor’s plugin API for extending existing blocks
- Creating a proper plugin structure
- Testing compatibility with each Redactor version
This is a larger effort but would make upgrades trivial.
Related Files
Section titled “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