javascriptsummernoteroxy-fileman

How to add image manager Roxy Fileman in Summernote WYSIWYG editor?


In my project I use Summernote editor but it doesn't have any image management plugin like TinyMCE or CKEditor.

My web application has a set of articles that users can edit. I would like to have a feature that allows the user to add images to the text area while editing an article in Summernote. However, these images need to be uploaded to the server. Roxy Fileman does this very well so I would like to integrate it.

Can anyone suggest how to implement this?

Since I can't use the standard image insertion dialog, I want to implement it through a separate button:

//My button
var RoxyButton = function (context) {
  var ui = $.summernote.ui;

  // create button
  var button = ui.button({
    contents: '<i class="fa fa-child"/>',
    tooltip: 'Insert image',
    click: RoxyFilemanDialog
  });

  return button.render(); // return button as jquery object
}

This is what the settings configuration for Summernote will look like:

toolbar: [
  ['mybutton', ['roxy']],
],
buttons: {
    roxy: RoxyButton
},

When I press my button, the dialog function should be triggered:

// Summernote
function RoxyFilemanDialog@(callback, value, type){    
    $.ajax({
        cache: false,
        type: "GET",
        url: "@Url.Action("CreateConfiguration", "RoxyFileman")",
        success: function (data, textStatus, jqXHR) {
            var roxyFileman = '@Url.Content("~/lib/Roxy_Fileman/index.html")'; 

//There should be a call to the function to open a dialog in Summernote...
{...
           
            $.ajax({
                title: 'Roxy Fileman',
                url: roxyFileman,
                width: 850,
                height: 650,
                plugins: "media",                
            });
}
            return false;
        },
        error: function (jqXHR, textStatus, errorThrown) {
            ...
        }
    });
}

But it seems that Summernote does not have an API for opening a custom dialog window and embedding your functions into it.

Or maybe I'm digging in the wrong direction and the implementation should be completely different?

Can anyone suggest how to implement this?


Solution

  • So I figured out what the problem was and now I'm ready to provide a solution. Overall, the answer from @zeros-n-ones was close to correct, except for one thing.

    I notice that the callback initialization happens in the dialog but the problem might be with the way RoxyFileman receives and processes it.

    The issue is that the RoxyFileman dialog is being opened in a modal with an iframe, but the ROXY_CALLBACK is being set in the parent window. Instead, we need to set it in the iframe's window context.

    Here is the full solution code:

    <script asp-location="Head">
        $(function() {
            // Summernote
            // Dialog handler
            function openRoxyDialog(context) {
                var roxyFileman = '@Url.Content("~/lib/Roxy_Fileman/index.html")';
    
                // Create modal dialog
                var dialog = $('<div class="modal fade" role="dialog">');
                var content = `
                  <div class="modal-dialog modal-lg">
                    <div class="modal-content">
                      <div class="modal-header">
                        <h4 class="modal-title">Select Image</h4>
                        <button type="button" class="close" data-dismiss="modal">&times;</button>
                      </div>
                      <div class="modal-body">
                        <iframe id="roxyFrame" width="100%" height="500" frameborder="0"></iframe>
                      </div>
                    </div>
                  </div>
                `;
                dialog.html(content);
    
                // Function to handle image selection
                function handleSelectedFile(file) {
    
                    if (file && file.fullPath) {
                        var fullImagePath = file.fullPath;
                        context.invoke('editor.insertImage', fullImagePath);
                    }
                    dialog.modal('hide');
                }
    
                // Load Roxy Fileman
                $.ajax({
                    cache: false,
                    type: "GET",
                    url: "@Url.Action("CreateConfiguration", "RoxyFileman")",
                    success: function (data) {
                        dialog.modal('show');
                        setTimeout(() => {
                            var iframe = document.getElementById('roxyFrame');
    
                            // Wait for iframe to load before setting up callback
                            iframe.onload = function() {
                                // Set callback in iframe's window context
                                iframe.contentWindow.ROXY_CALLBACK = handleSelectedFile;
                            };
    
                            // Set the iframe source after setting up onload handler
                            iframe.src = roxyFileman;
                         }, 1000);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        console.error('Failed to load Roxy configuration:', errorThrown);
                    }
                });
    
                // Clean up on close
                dialog.on('hidden.bs.modal', function () {
                    var iframe = document.getElementById('roxyFrame');
                    if (iframe && iframe.contentWindow) {
                        iframe.contentWindow.ROXY_CALLBACK = null;
                    }
                    dialog.remove();
                });
            }
    
            //Image button
            var RoxyImageButton = function (context) {
                var ui = $.summernote.ui;
    
                // create button
                return ui.button({
                    contents: '<i class="fa fa-image"/>',
                    tooltip: 'Insert image',
                    click: function () {
                        openRoxyDialog(context);
                    }
                }).render();
            };
    
            var initializationOptions = {
                toolbar: [
                    // other toolbar options...
                    ['insert', ['link', 'image', 'video', 'hr']]
                ],
                buttons: {
                    image: RoxyImageButton
                },                
                maxHeight: 800                
            };            
    
            $('#@Html.IdForModel()').summernote(initializationOptions);
        });
    </script>
    

    The Roxy configuration file (custom.js) must be updated to enable the callback:

    function FileSelected(file) {
      if (window.opener && window.opener.ROXY_CALLBACK) {
        window.opener.ROXY_CALLBACK(file);
        window.close();
      } else if (window.ROXY_CALLBACK) {
        window.ROXY_CALLBACK(file);
        window.close();
      } else {
        alert('"' + file.fullPath + '" selected. Integration callback not found.');
      }
    }
    

    That's all, I hope this will be useful to someone and save some time.