n2cms / n2cms

N2 CMS, an open source CMS for ASP.NET
http://n2cms.com
Other
363 stars 194 forks source link

Wysiwyg editor #159

Open GrimaceOfDespair opened 11 years ago

GrimaceOfDespair commented 11 years ago

I recently updated from an old version of N2, and I noticed ckEditor coming in. I welcome it, but it's a bit of a shame it was not refactored in such a way that one could easily toggle between the earlier-pretty-ok-working openWYSIWYG tinyMCE and the new editor.

I was able to tweak N2 to use openWYSIWYG tinyMCE again (I had some stuff hooked up specifically for that editor), so that's really an argument in favour of N2CMS, and it also shows it shouldn't be so hard to keep both alive.

I do realize the footprint of the deployment may be an issue. So actually, the refactoring might need to support providing an editor through a separate NuGet package.

Come to think of it: the idea of N2CMS has always been to keep things simple yet flexible. I could imagine a vanilla N2CMS without any WYSIWYG editor, just a TextArea. Adding a visual editor would then consist of merely adding the right NuGet package.

All of the above are thoughts without considering how deeply the editor is integrate into the system, so I have no idea atm what kind of work would be involved.

bherila commented 11 years ago

The RawHTML part gives you the simple text editor you're looking for. What is it about CKEditor that you find lacking?

bherila commented 11 years ago

some stuff hooked up specifically for that editor

Anything you could share that we can help integrate? There are some more major changes coming if you read the other issues and we'd love to help not break your scenarios :-)

GrimaceOfDespair commented 11 years ago

Well, actually we hooked up an alternative item selector for images, based on (an old version of) Teleriks ImageManager.

Connecting the right item selector with the right tinyMCE dialog was not so straightforward, because by default, there's only dialog callback. We had to generate javascript callback functions with dynamic names, so we could differentiate between the different initiating dialogs. I do not consider what we have as a structural solution nor would I like to promote it as such. So while I don't mind if our scenario gets broken, I would like to be able to solve it when it does:)

Cutting to the chase: if the editor implementation would allow for different, configurable type of selectors (internal links, images, media, ... ), that would be enough. The default implementation would then just open up the wellknown tree dialog, and along the way, ppl could contribute refined dialogs per selector type.

GrimaceOfDespair commented 11 years ago

To stil give you some clue to our implementation:

public class EnhancedEditableFreeTextAreaAttribute : N2.Details.EditableFreeTextAreaAttribute
{
    public string OnCloseDialogImageDocument { get; private set; }
    public string OnOpenDialog { get; set; }

    public EnhancedEditableFreeTextAreaAttribute()
    {
      OnCloseDialogImageDocument =
        "$('#mceModalBlocker,body>div[id^=mce_]').show();"+
        "if (value) { srcField.value = value; }";

      OnOpenDialog =
        "$('#mceModalBlocker,body>div[id^=mce_]').hide();";
    }

    protected override Control AddEditor(Control container)
    {
      var editor = base.AddEditor(container);

      var freeTextArea = editor as EnhancedFreeTextArea;
      if (freeTextArea != null)
      {

        var virtualPath = Engine.Resolve<PersonalPathProvider>().GetVirtualPath();

        var fileManagers = new Dictionary<string, IFileManager>
                       {
                         {"file", new DocumentManager()},
                         {"image", new ImageManager()},
                       };

        foreach (var fileManager in fileManagers.Select(fileManagerData => fileManagerData.Value))
        {
          fileManager.OnClientOpen = OnOpenDialog;
          fileManager.OnClientCallback = OnCloseDialogImageDocument;
          fileManager.VirtualPath = virtualPath;
          container.Controls.Add(fileManager);
        }

        editor.Load += (sender, args) => freeTextArea.CustomOverrides.Add(
          "file_browser_callback",
          InstallFileBrowserScript(freeTextArea, fileManagers));
      }

      return editor;
    }

    public string InstallFileBrowserScript(EnhancedFreeTextArea editor, Dictionary<string, IFileManager> fileManagers)
    {
      // Revert to default N2 callback if none specified
      if (fileManagers.Any() == false) return "fileBrowserCallBack";

      var functionName = editor.ClientID + "_browser_callback";
      var defaultManager = fileManagers.First();

      var fileBrowserScript =
        "function " + functionName + "(field_name, url, destinationType, win) {" +
          "srcField = win.document.forms[0].elements[field_name];" +
          "switch (destinationType) {" +

            string.Concat(fileManagers.Skip(1).Select(m =>
              "case '" + m.Key + "': " +
                m.Value.OpenCallback + "(); " +
                "break;")
                .ToArray()) +

              "default:" +
                defaultManager.Value.OpenCallback + "();" +
                "break;" +
          "}" +
        "}";

      editor.Page.JavaScript(fileBrowserScript, ScriptOptions.ScriptTags);

      return functionName;
    }

}
bherila commented 11 years ago

Generically, parts get their rich text editor through the FreeTextArea attribute. I can see a possibility of implementing the FreeTextArea behind the attribute as a N2 service using inversion of control and then overriding methods to replace things like the image editor.