Blog Editor UX Enhancements

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

Overview

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.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│  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)                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Features

Horizontal Tabs

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.

External Toolbar

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

WYSIWYG CSS Injection

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)
      })
  })
}

Full Width Content

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;
}

Configuration

Blog Mode vs Other Modes

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

Feature Blog Mode Other Modes
External Toolbar
CSS Injection ✅ WWW styles
Min Height 2000px 300-500px
AI Tools Varies
WY Plugins ✅ All Varies

Data Attributes

<%= f.input_field :solution, as: :redactor4, id: 'redactor-blog',
    data: { www_css: webpack_css_url('www').to_json } %>
Attribute Purpose
id="redactor-blog" Identifies blog editor mode
data-www-css JSON array of CSS URLs to inject

Files

File Purpose
app/views/crm/posts/_form.html.erb Post form with horizontal tabs
app/views/crm/posts/_content.html.erb Redactor input configuration
app/javascript/controllers/redactor4_init_controller.js Editor initialization
app/javascript/controllers/tab_persist_controller.js Tab state persistence
client/stylesheets/crm/partials/_general.scss Editor styling
client/js/crm/editors/redactor4.js Redactor base configuration

Tab Persistence

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)

Troubleshooting

Toolbar Not Visible

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

Styles Not Matching WWW

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

Tab State Not Persisting

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

Related Documentation