feat: replace custom rich text editor with WordPress TinyMCE and add markdown conversion
- Replace custom contenteditable rich text editor with WordPress native TinyMCE editor - Implement comprehensive markdown to HTML conversion for AI responses - Support headers (H1-H3), bold/italic text, bullet lists, and paragraphs - Integrate markdown conversion into AI Assistant response handling - Maintain backward compatibility with existing textarea fallback 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
00f88070b8
commit
b7e5514e8e
2 changed files with 133 additions and 39 deletions
|
|
@ -568,17 +568,20 @@ jQuery(document).ready(function($) {
|
|||
|
||||
// Apply description (handle TinyMCE, regular textarea, and rich text editor)
|
||||
if (data.description) {
|
||||
// Convert markdown to HTML for proper rich text editor formatting
|
||||
const htmlContent = this.markdownToHtml(data.description);
|
||||
|
||||
// Try TinyMCE first if available
|
||||
if (typeof tinyMCE !== 'undefined' && tinyMCE.get('event_description')) {
|
||||
tinyMCE.get('event_description').setContent(data.description);
|
||||
tinyMCE.get('event_description').setContent(htmlContent);
|
||||
} else {
|
||||
// Update the hidden textarea
|
||||
$('#event_description, [name="event_description"]').val(data.description);
|
||||
// Update the hidden textarea with HTML content
|
||||
$('#event_description, [name="event_description"]').val(htmlContent);
|
||||
|
||||
// Also update the visible rich text editor div if it exists
|
||||
const $richEditor = $('#event-description-editor');
|
||||
if ($richEditor.length && $richEditor.is('[contenteditable]')) {
|
||||
$richEditor.html(data.description);
|
||||
$richEditor.html(htmlContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -707,6 +710,85 @@ jQuery(document).ready(function($) {
|
|||
alert(message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert markdown to HTML for rich text editor
|
||||
*/
|
||||
markdownToHtml: function(markdown) {
|
||||
let html = markdown;
|
||||
|
||||
// Convert headers (## -> h2, ### -> h3, etc.)
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// Convert bold text (**text** -> <strong>text</strong>)
|
||||
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Convert italic text (*text* -> <em>text</em>)
|
||||
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
||||
|
||||
// Convert bullet lists (* item -> <ul><li>item</li></ul>)
|
||||
html = html.replace(/^\* (.+)$/gm, '<li>$1</li>');
|
||||
|
||||
// Wrap consecutive <li> items in <ul> tags
|
||||
html = html.replace(/(<li>.*<\/li>)/gs, function(match) {
|
||||
if (!match.includes('<ul>')) {
|
||||
return '<ul>' + match + '</ul>';
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
// Convert line breaks to <p> tags for paragraphs
|
||||
const lines = html.split('\n');
|
||||
const paragraphs = [];
|
||||
let currentParagraph = '';
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
// Skip empty lines
|
||||
if (line === '') {
|
||||
if (currentParagraph) {
|
||||
paragraphs.push(currentParagraph);
|
||||
currentParagraph = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If line is already wrapped in HTML tags, add it as is
|
||||
if (line.match(/^<(h[1-6]|ul|li|strong|em)/)) {
|
||||
if (currentParagraph) {
|
||||
paragraphs.push(currentParagraph);
|
||||
currentParagraph = '';
|
||||
}
|
||||
paragraphs.push(line);
|
||||
} else {
|
||||
// Regular text line
|
||||
if (currentParagraph) {
|
||||
currentParagraph += ' ' + line;
|
||||
} else {
|
||||
currentParagraph = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add final paragraph if exists
|
||||
if (currentParagraph) {
|
||||
paragraphs.push(currentParagraph);
|
||||
}
|
||||
|
||||
// Wrap non-HTML paragraphs in <p> tags
|
||||
const formattedParagraphs = paragraphs.map(p => {
|
||||
if (p.match(/^<(h[1-6]|ul|li)/)) {
|
||||
return p;
|
||||
} else {
|
||||
return '<p>' + p + '</p>';
|
||||
}
|
||||
});
|
||||
|
||||
return formattedParagraphs.join('\n');
|
||||
},
|
||||
|
||||
/**
|
||||
* Escape HTML for safe display
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -307,44 +307,11 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|||
'required' => true,
|
||||
]);
|
||||
|
||||
// Event description with rich text editor
|
||||
// Event description with WordPress rich text editor
|
||||
$description_field = [
|
||||
'type' => 'custom',
|
||||
'name' => 'event_description',
|
||||
'custom_html' => '<div class="form-row event-description-wrapper">
|
||||
<label for="event_description"><strong>Event Description</strong></label>
|
||||
<div id="event-description-editor-wrapper" class="rich-text-editor-wrapper">
|
||||
<div id="event-description-toolbar" class="rich-text-toolbar">
|
||||
<div class="toolbar-group">
|
||||
<button type="button" data-command="bold" title="Bold"><strong>B</strong></button>
|
||||
<button type="button" data-command="italic" title="Italic"><em>I</em></button>
|
||||
<button type="button" data-command="underline" title="Underline"><u>U</u></button>
|
||||
</div>
|
||||
<div class="toolbar-group">
|
||||
<button type="button" data-command="insertUnorderedList" title="Bullet List">• List</button>
|
||||
<button type="button" data-command="insertOrderedList" title="Numbered List">1. List</button>
|
||||
</div>
|
||||
<div class="toolbar-group">
|
||||
<button type="button" data-command="createLink" title="Insert Link">🔗 Link</button>
|
||||
<button type="button" data-command="unlink" title="Remove Link">🔗✗</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="event-description-editor"
|
||||
class="rich-text-editor"
|
||||
contenteditable="true"
|
||||
data-placeholder="Describe your event... Include key details like what attendees will learn, what to bring, prerequisites, and any special requirements."
|
||||
style="min-height: 200px; border: 1px solid #ddd; padding: 15px; border-radius: 4px;"
|
||||
></div>
|
||||
<textarea
|
||||
name="event_description"
|
||||
id="event_description"
|
||||
style="display: none;"
|
||||
maxlength="5000"
|
||||
></textarea>
|
||||
</div>
|
||||
<small class="description">Use the toolbar above to format your event description. Character limit: 5000</small>
|
||||
</div>',
|
||||
'custom_html' => $this->render_wp_editor_field(),
|
||||
'wrapper_class' => 'form-row event-description-field'
|
||||
];
|
||||
|
||||
|
|
@ -1665,4 +1632,49 @@ HTML;
|
|||
</div>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render WordPress rich text editor field
|
||||
*
|
||||
* @return string HTML for WordPress editor
|
||||
*/
|
||||
private function render_wp_editor_field(): string {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="form-row event-description-wrapper">
|
||||
<label for="event_description"><strong>Event Description</strong></label>
|
||||
<?php
|
||||
$editor_settings = [
|
||||
'textarea_name' => 'event_description',
|
||||
'textarea_rows' => 10,
|
||||
'media_buttons' => false,
|
||||
'teeny' => false,
|
||||
'tinymce' => [
|
||||
'toolbar1' => 'formatselect,bold,italic,underline,strikethrough,|,bullist,numlist,|,link,unlink,|,blockquote,hr,|,alignleft,aligncenter,alignright,|,undo,redo',
|
||||
'toolbar2' => '',
|
||||
'block_formats' => 'Paragraph=p;Heading 2=h2;Heading 3=h3;Heading 4=h4;Preformatted=pre',
|
||||
'forced_root_block' => 'p',
|
||||
'force_p_newlines' => true,
|
||||
'remove_redundant_brs' => true,
|
||||
'convert_urls' => false,
|
||||
'paste_as_text' => false,
|
||||
'paste_auto_cleanup_on_paste' => true,
|
||||
'paste_remove_spans' => true,
|
||||
'paste_remove_styles' => true,
|
||||
'paste_strip_class_attributes' => 'all',
|
||||
'valid_elements' => 'p,br,strong,em,ul,ol,li,h2,h3,h4,h5,h6,blockquote,a[href|title],hr',
|
||||
'valid_children' => '+p[strong|em|a|br],+ul[li],+ol[li],+li[strong|em|a|br|p]'
|
||||
],
|
||||
'quicktags' => [
|
||||
'buttons' => 'strong,em,ul,ol,li,link,close'
|
||||
]
|
||||
];
|
||||
|
||||
wp_editor('', 'event_description', $editor_settings);
|
||||
?>
|
||||
<small class="description">Use the editor above to format your event description with headings, lists, and formatting.</small>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue