Blog Editor UX Enhancements
Status: Production Ready
Version: 1.0.0
Last Updated: January 2025
Overview
Section titled “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
Section titled “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
Section titled “Features”Horizontal Tabs
Section titled “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
Section titled “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
Section titled “WYSIWYG CSS Injection”The editor content area is styled to match the live blog:
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
Section titled “Full Width Content”CSS ensures the editor uses all available space:
.rx-container { max-width: 100% !important; width: 100% !important;}
.rx-editor,.rx-content { max-width: 100% !important;}Configuration
Section titled “Configuration”Blog Mode vs Other Modes
Section titled “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
Section titled “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 |
| 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
Section titled “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
Section titled “Troubleshooting”Toolbar Not Visible
Section titled “Toolbar Not Visible”Cause: External toolbar container missing
Fix: Ensure #redactor-blog-toolbar div exists before the editor
Styles Not Matching WWW
Section titled “Styles Not Matching WWW”Cause: CSS injection failed or wrong URLs
Fix: Check webpack_css_url('www') returns valid URLs
Tab State Not Persisting
Section titled “Tab State Not Persisting”Cause: Bootstrap Tab API not loaded
Fix: Ensure Bootstrap JS is loaded before form submission
Related Documentation
Section titled “Related Documentation”- Redactor Configuration - All editor modes
- WY Image Plugin - Rich image insertion
- UI Conventions - Bootstrap patterns