Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions gravity-forms/gw-rich-text-html-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,31 @@
</style>

<script>
// Immediately disable HTML click.
jQuery( '.gf-html-container' )
.css( 'pointer-events', 'none' )
.css( 'opacity', '0.6' );

// Re-enable only after full load of page.
jQuery( window ).on( 'load', function() {
setTimeout( function() {
jQuery( '.gf-html-container' )
.css( 'pointer-events', 'auto' )
.css( 'opacity', '1' );
}, 800 );
});

var gwRichTextMode;
jQuery( document ).on( 'gform_load_field_settings', function( event, field ) {
gwRichTextMode = field.gwRichTextMode || 'tmce';

var id = 'field_rich_content';
wp.editor.remove( id );
jQuery( '#' + id ).val( field.content );
wp.editor.initialize( id, {
tinymce: {
forced_root_block: false,
height: 250,
setup: function( editor ) {
editor.on( 'Paste Change input Undo Redo', function () {
SetFieldProperty( 'content', editor.getContent() );
Expand All @@ -75,6 +94,7 @@
quicktags: true
} );
} );

jQuery( document).on( 'tinymce-editor-setup', function ( event, editor ) {
var editorId = 'field_rich_content';
if ( editor.id === editorId ) {
Expand Down Expand Up @@ -108,11 +128,49 @@
}
} );

// Wait until the TinyMCE editor is initialized before switching mode.
const waitForEditorToBeReady = (callback, timeout = 5000) => {
const start = Date.now();
const interval = setInterval(() => {
const editor = typeof tinymce !== 'undefined' && tinymce.get(editorId);
if (editor) {
clearInterval(interval);
callback();
} else if (Date.now() - start > timeout) {
clearInterval(interval);
}
}, 100);
};

waitForEditorToBeReady(() => window.switchEditors.go(editorId, gwRichTextMode === 'html' ? 'html' : 'tmce'));

// Set the content when save.
window.SetFieldContentProperty = function () {
var mode = jQuery('#wp-' + editorId + '-wrap').hasClass('html-active') ? 'html' : 'tmce';
var content = '';

if (mode === 'html') {
content = jQuery('#' + editorId).val();
} else if (tinymce.get(editorId)) {
content = tinymce.get(editorId).getContent();
}

SetFieldProperty('content', content);
};
Comment on lines +147 to +159
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential content loss if TinyMCE not ready in visual mode.

When mode === 'tmce' but tinymce.get(editorId) returns falsy, content remains an empty string and SetFieldProperty is still called, potentially wiping existing content. Consider falling back to the textarea value:

 				if (mode === 'html') {
 					content = jQuery('#' + editorId).val();
 				} else if (tinymce.get(editorId)) {
 					content = tinymce.get(editorId).getContent();
+				} else {
+					// Fallback to textarea if TinyMCE not ready
+					content = jQuery('#' + editorId).val();
 				}
🤖 Prompt for AI Agents
In gravity-forms/gw-rich-text-html-fields.php around lines 147 to 159, the
SetFieldContentProperty function can set content to an empty string when mode
=== 'tmce' but tinymce.get(editorId) is falsy, risking data loss; change the
logic so that when tinymce is not available you fall back to the textarea value
(jQuery('#' + editorId).val()) instead of leaving content empty, then call
SetFieldProperty('content', content) with that fallback value.


// Update the content.
jQuery(document).on('change', `#${editorId}`, function () {
window.SetFieldContentProperty();
});
Comment on lines +161 to +164
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use input event instead of change for real-time sync; prevent duplicate handlers.

The change event only fires when the textarea loses focus, so keystrokes won't sync until the user clicks away. This could explain the reported line break issues. Additionally, this delegated handler isn't cleaned up on editor reinitialization, leading to duplicate handlers.

+			// Namespace the event for proper cleanup
+			jQuery(document).off('input.gwRichText', `#${editorId}`);
-			jQuery(document).on('change', `#${editorId}`, function () {
+			jQuery(document).on('input.gwRichText', `#${editorId}`, function () {
 				window.SetFieldContentProperty();
 			});
🤖 Prompt for AI Agents
In gravity-forms/gw-rich-text-html-fields.php around lines 147 to 150, the
delegated handler uses the 'change' event (which only fires on blur) and can be
bound repeatedly on reinitialization; switch to the 'input' event for real-time
keystroke syncing and prevent duplicate handlers by removing any existing
handler before binding (use a namespaced event or call off(...) with the same
selector/event signature prior to on(...)). Ensure the new binding targets the
correct element and that cleanup happens on editor teardown or before
reinitialization to avoid multiple listeners.


// Switch to visual/text mode.
jQuery(`#wp-${editorId}-wrap .switch-tmce, #wp-${editorId}-wrap .switch-html`).on('click', function() {
var mode = jQuery(this).hasClass('switch-tmce') ? 'tmce' : 'html';

window.switchEditors.go(editorId, mode);

// Save the current mode to field property.
SetFieldProperty('gwRichTextMode', mode)
});
Comment on lines 166 to 174
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Sync content before switching modes to prevent data loss.

When switching from HTML to visual mode (or vice versa), the current content should be saved first. Otherwise, unsaved changes in the active mode could be lost during the switch.

 				jQuery(`#wp-${editorId}-wrap .switch-tmce, #wp-${editorId}-wrap .switch-html`).on('click', function() {
 					var mode = jQuery(this).hasClass('switch-tmce') ? 'tmce' : 'html';
 
+					// Sync content before switching to preserve unsaved changes
+					window.SetFieldContentProperty();
+
 					window.switchEditors.go(editorId, mode);
 
 					// Save the current mode to field property.
-					SetFieldProperty('gwRichTextMode', mode)
+					SetFieldProperty('gwRichTextMode', mode);
 				});

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In gravity-forms/gw-rich-text-html-fields.php around lines 152-160, the click
handler switches editor modes without syncing the current editor content,
risking data loss; before calling window.switchEditors.go(editorId, mode) detect
the active mode and capture the current content (from tinyMCE when visual/tmce
is active, or from the textarea when HTML is active), write that content back
into the underlying textarea and/or the field value used by Gravity Forms, then
persist any mode property (SetFieldProperty('gwRichTextMode', mode)) and only
after syncing call switchEditors.go so the editor switch preserves unsaved
changes.

}
} );
Expand Down