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
- 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
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:
- 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
- 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:
- 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
- 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