Skip to content

gw-rich-text-html-fields.php: Added functionality to remember the last selected Visual or HTML editor mode. #1071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

malayladu
Copy link
Contributor

@malayladu malayladu commented Apr 10, 2025

Context

⛑️ Ticket(s): https://secure.helpscout.net/conversation/2901552081/81917

Summary

This PR fixes this two issues.

  1. After saving in Text mode, the editor reverts back to Visual mode.
  2. After saving in Text mode, the content entered in Text mode is not saved.

@malayladu malayladu self-assigned this Apr 10, 2025
Copy link

coderabbitai bot commented Apr 10, 2025

Walkthrough

This change updates the editor functionality in the gravity-forms/gw-rich-text-html-fields.php file. It introduces persistent editor mode handling by storing the user’s last selected mode (tmce or html) in a field property (gwRichTextMode) keyed by form and field IDs. The code waits asynchronously for the TinyMCE editor to be fully initialized before switching to the saved mode. A global function SetFieldContentProperty is added to update the field content based on the current editor mode, triggered on editor input events. Mode switches are saved back to the field property, enabling mode persistence and restoration across sessions.

Changes

File Change Summary
gravity-forms/gw-rich-text-html-fields.php Added tracking of current form and field IDs and editor mode; retrieved and applied saved editor mode from field property on editor load; implemented asynchronous wait for TinyMCE readiness before mode switching; introduced global SetFieldContentProperty function to update field content based on active editor mode; added event listeners to update content on input and save mode on switching.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditorScript
    participant FieldPropertyStore
    participant EditorInstance

    User->>EditorScript: Load form field settings
    EditorScript->>FieldPropertyStore: Retrieve saved editor mode (gwRichTextMode)
    EditorScript->>EditorInstance: waitForEditorToBeReady()
    EditorInstance-->>EditorScript: Editor ready
    EditorScript->>EditorInstance: Switch editor to saved mode (tmce or html)
Loading
sequenceDiagram
    participant User
    participant Editor
    participant EventListener
    participant SetFieldContentProperty
    participant FieldPropertyStore

    User->>Editor: Input or switch editor mode
    Editor->>EventListener: Detect input or mode change
    EventListener->>SetFieldContentProperty: Trigger content update
    alt HTML mode active
        SetFieldContentProperty->>Editor: Get plain text content
    else TinyMCE mode active
        SetFieldContentProperty->>Editor: Get TinyMCE content
    end
    SetFieldContentProperty->>FieldPropertyStore: Save current editor mode (gwRichTextMode)
Loading

Possibly related PRs

Suggested reviewers

  • saifsultanc

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7547d70 and 814c201.

📒 Files selected for processing (1)
  • gravity-forms/gw-rich-text-html-fields.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • gravity-forms/gw-rich-text-html-fields.php

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Apr 10, 2025

Warnings
⚠️ When ready, don't forget to request reviews on this pull request from your fellow wizards.

Generated by 🚫 dangerJS against 814c201

@spivurno
Copy link
Contributor

@malayladu Not a review, just a hot thought – my instinct would be to save this to the field in the form meta. This way if they open the field in another browser they'll still have the same preference set.

@malayladu malayladu force-pushed the malay/fix/81917-content-save-issue branch 2 times, most recently from 0af90a0 to 7541990 Compare April 11, 2025 07:06
@malayladu malayladu marked this pull request as ready for review April 11, 2025 07:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
gravity-forms/gw-rich-text-html-fields.php (5)

75-77: Good implementation of editor mode persistence

Storing the previously selected editor mode in localStorage is a good approach for persisting user preferences across page reloads.

Consider implementing server-side persistence as mentioned in the PR comments by spivurno - saving this preference to the form meta would ensure consistency across different browsers/devices.


78-86: Add timeout mechanism to the editor readiness check

The polling approach to check if the TinyMCE editor is initialized is good, but could be improved.

Add a timeout mechanism to prevent potential infinite polling:

function waitForEditorToBeReady(callback) {
+   var maxAttempts = 50; // 5 seconds max wait time
+   var attempts = 0;
    var interval = setInterval(function () {
+       attempts++;
        if (typeof tinymce !== 'undefined' && tinymce.get(editorId)) {
            clearInterval(interval);
            callback();
        }
+       else if (attempts >= maxAttempts) {
+           clearInterval(interval);
+           console.warn('Editor initialization timed out');
+       }
    }, 100);
}

88-94: Be explicit about the default mode condition

The code switches to the saved mode or defaults to Visual mode if the saved mode isn't 'html'.

Make the intent clearer by explicitly checking for both possible values:

waitForEditorToBeReady(function () {
    if (savedMode === 'html') {
        window.switchEditors.go(editorId, 'html');
-   } else {
+   } else {  // Default to visual mode if savedMode is null, undefined, or 'tmce'
        window.switchEditors.go(editorId, 'tmce');
    }
});

96-108: Consider namespacing the global function

Creating a function to update content based on the current mode is a good approach.

However, exposing the function directly on the window object could lead to naming conflicts. Consider namespacing it:

- window.SetFieldContentProperty = function () {
+ window.GravityWizRichText = window.GravityWizRichText || {};
+ window.GravityWizRichText.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);
};

Then update all references to this function accordingly.


110-113: Optimize event binding and ensure it handles all input cases

The current implementation binds to the change event, which may not capture all user interactions.

Consider using more specific event binding and adding additional events to catch all user inputs:

// Update the content.
- jQuery(document).on('change', `#${editorId}`, function () {
-    window.SetFieldContentProperty();
+ jQuery(`#${editorId}`).on('change input keyup', function () {
+    window.SetFieldContentProperty();
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0390f25 and 7541990.

📒 Files selected for processing (1)
  • gravity-forms/gw-rich-text-html-fields.php (1 hunks)
🔇 Additional comments (1)
gravity-forms/gw-rich-text-html-fields.php (1)

121-122: Good implementation of mode persistence

Saving the current mode to localStorage when switching editors ensures the preference is maintained.

Consider testing this implementation across different browsers to ensure consistent behavior, particularly older browsers that may have limited localStorage support.

@malayladu
Copy link
Contributor Author

malayladu commented Apr 11, 2025

@malayladu Not a review, just a hot thought – my instinct would be to save this to the field in the form meta. This way if they open the field in another browser they'll still have the same preference set.

@spivurno That’s a great idea for keeping the choice persistent across devices and browsers. However, to make that work, we’d also need to save user-specific preferences for the form. If multiple people are working on the same form, we’ll need to store preferences individually for each user.

Do we want to implement this?

@saifsultanc
Copy link
Contributor

However, to make that work, we’d also need to save user-specific preferences for the form. If multiple people are working on the same form, we’ll need to store preferences individually for each user.

Do we want to implement this?

You are right. Since form meta applies globally, we'd need to introduce a way to store these preferences on a per user basis. We could potentially explore storing the preference using user meta key to the form ID?

@malayladu malayladu force-pushed the malay/fix/81917-content-save-issue branch from 7541990 to 7547d70 Compare April 15, 2025 15:00
@spivurno
Copy link
Contributor

spivurno commented Apr 15, 2025

@malayladu @saifsultanc @claygriffiths I think I understand the confusion on my original feedback that this should be saved to the form meta.

By default, WordPress' TinyMCE implementation does treat visual vs text editing as a user-specific preference. And it makes sense that we might want to preserve that.

My concern is that toggling between these views can actually break custom markup as the visual editor will transform the text-based content into its own desired format.

This makes me want to break with the norm here and make the format (visual vs text) a field-specific setting rather than a user-specific preference.

Thoughts?

EDIT:

Just wanted to add that this snippet is proving to be quite popular and gaining a lot of interest so I think it's worth getting this right. If we need to punt it in favor of more pressing tickets that's totally fine – but hopefully not for too long. 😄

@malayladu
Copy link
Contributor Author

@malayladu @saifsultanc @claygriffiths I think I understand the confusion on my original feedback that this should be saved to the form meta.

By default, WordPress' TinyMCE implementation does treat visual vs text editing as a user-specific preference. And it makes sense that we might want to preserve that.

My concern is that toggling between these views can actually break custom markup as the visual editor will transform the text-based content into its own desired format.

This makes me want to break with the norm here and make the format (visual vs text) a field-specific setting rather than a user-specific preference.

Thoughts?

EDIT:

Just wanted to add that this snippet is proving to be quite popular and gaining a lot of interest so I think it's worth getting this right. If we need to punt it in favor of more pressing tickets that's totally fine – but hopefully not for too long. 😄

@spivurno With the latest code update, the editor mode setting is now saved separately for each field

@spivurno
Copy link
Contributor

@malayladu What I'm saying is that local storage doesn't solve the problem we're trying to solve here. Local storage is inherently a user preference. Many different users will edit a given form and interact with these fields. If this is a user preference than the carefully crafted markup from User A could be unintentionally undone by User B just by loading the field settings for a Rich Text HTML field.

Some examples from the wild:

  1. https://stackoverflow.com/questions/44000699/how-to-make-wordpress-editor-tinymce-preserve-html-formatting-when-switching-b
  2. https://wordpress.stackexchange.com/questions/214588/tinymce-editor-is-breaking-my-beautiful-html
  3. HTML stripped when switching between Visual & Text WordPress/classic-editor#155

I don't think we'll need any fancy code to pull this off. Here's what I'm thinking.

  1. Whenever the field is toggled to visual or text mode, we set a property on the field object via JS (no AJAX required).
  2. When the form is saved, the current mode is saved as part of the field meta automatically (no extra code required).
  3. When the field settings are loaded for an HTML field, the property is checked and the editor is loaded in the last saved mode based on the field property.

In this scenario, I don't think we need local storage at all.

Thoughts?

@malayladu
Copy link
Contributor Author

@malayladu What I'm saying is that local storage doesn't solve the problem we're trying to solve here. Local storage is inherently a user preference. Many different users will edit a given form and interact with these fields. If this is a user preference than the carefully crafted markup from User A could be unintentionally undone by User B just by loading the field settings for a Rich Text HTML field.

Some examples from the wild:

  1. https://stackoverflow.com/questions/44000699/how-to-make-wordpress-editor-tinymce-preserve-html-formatting-when-switching-b
  2. https://wordpress.stackexchange.com/questions/214588/tinymce-editor-is-breaking-my-beautiful-html
  3. HTML stripped when switching between Visual & Text WordPress/classic-editor#155

I don't think we'll need any fancy code to pull this off. Here's what I'm thinking.

  1. Whenever the field is toggled to visual or text mode, we set a property on the field object via JS (no AJAX required).
  2. When the form is saved, the current mode is saved as part of the field meta automatically (no extra code required).
  3. When the field settings are loaded for an HTML field, the property is checked and the editor is loaded in the last saved mode based on the field property.

In this scenario, I don't think we need local storage at all.

Thoughts?

Ah, I see now. We want the editor mode setting to be based on the field, not the user. So once it's saved, any user who loads that field will see it in the Visual or HTML mode it was last saved in.

@spivurno
Copy link
Contributor

@malayladu Exactly! 😄

…ast selected `Visual` or `HTML` editor mode.
@malayladu malayladu force-pushed the malay/fix/81917-content-save-issue branch from 7547d70 to 814c201 Compare April 17, 2025 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants