Skip to content

Blog Editor UX Enhancements

Status: Production Ready
Version: 1.0.0
Last Updated: January 2025

The blog editor has been redesigned with improved UX patterns including horizontal tabs, an external toolbar, and WYSIWYG styling that matches the live site. These changes provide a more intuitive editing experience while maximizing content area space.

┌─────────────────────────────────────────────────────────────────┐
│ Horizontal Tab Navigation (Bootstrap 5) │
│ [Summary] [Content] [Tagging] [Status] [Related] [Advanced] │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ External Toolbar Container (#redactor-blog-toolbar) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Bold | Italic | ... | WY Image | WY Video | WY FAQ | AI... ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Redactor Content Area │
│ (Full width, WWW styles injected) │
│ │
└─────────────────────────────────────────────────────────────────┘

The post form uses Bootstrap 5 horizontal tabs instead of vertical navigation:

  • Summary - Title, description, author, dates
  • Content - Redactor editor (full width)
  • Tagging - Tags and categories
  • Status - Publication state, scheduling
  • Related - Related articles
  • Advanced - Content processing, TOC, schema markup

Tab state persists across form submissions via tab_persist_controller.js.

The Redactor toolbar is rendered outside the editor in a dedicated container:

<%# app/views/crm/posts/_form.html.erb %>
<div id="redactor-blog-toolbar" class="mb-3"></div>

Benefits:

  • Stays visible when scrolling
  • Doesn’t interfere with content area
  • Consistent positioning

The editor content area is styled to match the live blog:

redactor4_init_controller.js
if (isBlogEditor && editor.id === 'redactor-blog') {
// Fetch WWW CSS and inject into Redactor content area
const cssUrls = JSON.parse(editor.dataset.wwwCss || '[]')
cssUrls.forEach(url => {
fetch(url)
.then(response => response.text())
.then(css => {
const style = document.createElement('style')
style.textContent = css
rxContent.appendChild(style)
})
})
}

CSS ensures the editor uses all available space:

_general.scss
.rx-container {
max-width: 100% !important;
width: 100% !important;
}
.rx-editor,
.rx-content {
max-width: 100% !important;
}

The Redactor initializer detects blog mode via editor.id === 'redactor-blog':

FeatureBlog ModeOther Modes
External Toolbar
CSS Injection✅ WWW styles
Min Height2000px300-500px
AI ToolsVaries
WY Plugins✅ AllVaries
<%= f.input_field :solution, as: :redactor4, id: 'redactor-blog',
data: { www_css: webpack_css_url('www').to_json } %>
AttributePurpose
id="redactor-blog"Identifies blog editor mode
data-www-cssJSON array of CSS URLs to inject
FilePurpose
app/views/crm/posts/_form.html.erbPost form with horizontal tabs
app/views/crm/posts/_content.html.erbRedactor input configuration
app/javascript/controllers/redactor4_init_controller.jsEditor initialization
app/javascript/controllers/tab_persist_controller.jsTab state persistence
client/stylesheets/crm/partials/_general.scssEditor styling
client/js/crm/editors/redactor4.jsRedactor base configuration

The tab_persist_controller saves and restores active tab across page loads:

// Supports both <a href="#id"> and <button data-bs-target="#id">
const activeTabId = activeTab.getAttribute('href')?.replace('#', '') ||
activeTab.getAttribute('data-bs-target')?.replace('#', '')
sessionStorage.setItem('activePostTab', activeTabId)

Cause: External toolbar container missing
Fix: Ensure #redactor-blog-toolbar div exists before the editor

Cause: CSS injection failed or wrong URLs
Fix: Check webpack_css_url('www') returns valid URLs

Cause: Bootstrap Tab API not loaded
Fix: Ensure Bootstrap JS is loaded before form submission