Index: editors/fckeditor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/fckeditor.inc,v
retrieving revision 1.11
diff -u -p -r1.11 fckeditor.inc
--- editors/fckeditor.inc	13 Feb 2009 02:22:19 -0000	1.11
+++ editors/fckeditor.inc	20 Mar 2009 21:05:04 -0000
@@ -27,6 +27,14 @@ function wysiwyg_fckeditor_editor() {
     'themes callback' => 'wysiwyg_fckeditor_themes',
     'settings callback' => 'wysiwyg_fckeditor_settings',
     'plugin callback' => 'wysiwyg_fckeditor_plugins',
+    'plugin settings callback' => 'wysiwyg_fckeditor_plugin_settings',
+    'proxy plugin' => array(
+      'drupal' => array(
+        'load' => TRUE,
+        'proxy' => TRUE,
+      ),
+    ),
+    'proxy plugin settings callback' => 'wysiwyg_fckeditor_proxy_plugin_settings',
     'versions' => array(
       2.6 => array(
         'js files' => array('fckeditor-2.6.js'),
@@ -157,6 +165,35 @@ function wysiwyg_fckeditor_themes($edito
 }
 
 /**
+ * Build a JS settings array of native external plugins that need to be loaded separately.
+ *
+ * @todo Required for FCKeditor?  If not, wysiwyg_add_plugin_settings() needs
+ *   an overhaul.
+ */
+function wysiwyg_fckeditor_plugin_settings($editor, $profile, $plugins) {
+  $settings = array();
+  return $settings;
+}
+
+/**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_fckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
+  $settings = array();
+  foreach ($plugins as $name => $plugin) {
+    // Populate required plugin settings.
+    $settings[$name] = $plugin['dialog settings'] + array(
+      'title' => $plugin['title'],
+      'icon' => base_path() . $plugin['icon path'] .'/'. $plugin['icon file'],
+      'iconTitle' => $plugin['icon title'],
+      // @todo These should only be set if the plugin defined them.
+      'css' => base_path() . $plugin['css path'] .'/'. $plugin['css file'],
+    );
+  }
+  return $settings;
+}
+
+/**
  * Return internal plugins for FCKeditor; semi-implementation of hook_wysiwyg_plugin().
  */
 function wysiwyg_fckeditor_plugins($editor) {
Index: editors/js/fckeditor-2.6.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/fckeditor-2.6.js,v
retrieving revision 1.13
diff -u -p -r1.13 fckeditor-2.6.js
--- editors/js/fckeditor-2.6.js	5 Feb 2009 01:34:35 -0000	1.13
+++ editors/js/fckeditor-2.6.js	20 Mar 2009 21:05:04 -0000
@@ -11,13 +11,16 @@ Drupal.wysiwyg.editor.attach.fckeditor =
   if (settings.buttons) {
     FCKinstance.ToolbarSet = settings.ToolbarSet;
   }
-
-  // Apply input format configuration.
-  FCKinstance.Config.format = params.format;
-  delete settings.buttons;
-  for (var setting in settings) {
-    FCKinstance.Config[setting] = settings[setting];
+  // Store the current input format
+  FCKinstance.Config.wysiwygInputFormat = params.format;
+  // Point to custom settings file, loaded during editor init.
+  if(settings.CustomConfigurationsPath) {
+    FCKinstance.Config.CustomConfigurationsPath = settings.CustomConfigurationsPath;
   }
+ 
+  // Load Drupal plugins and apply format specific settings.
+  // @see fckeditor.config.js
+
   // Attach editor.
   FCKinstance.ReplaceTextarea();
 };
@@ -26,22 +29,139 @@ Drupal.wysiwyg.editor.attach.fckeditor =
  * Detach a single or all editors.
  */
 Drupal.wysiwyg.editor.detach.fckeditor = function(context, params) {
+  var instances = [];
   if (typeof params != 'undefined' && typeof FCKeditorAPI != 'undefined') {
     var instance = FCKeditorAPI.GetInstance(params.field);
     if (instance) {
-      $('#' + params.field).val(instance.GetXHTML()).show();
-      $('#' + params.field + '___Config').remove();
-      $('#' + params.field + '___Frame').remove();
-      delete FCKeditorAPI.__Instances[params.field];
+      instances[params.field] = instance;
     }
   }
   else {
-    for (var instance in FCKeditorAPI.__Instances) {
-      $('#' + instance).val(instance.GetXHTML()).show();
-      $('#' + instance + '___Config').remove();
-      $('#' + instance + '___Frame').remove();
-      delete FCKeditorAPI.__Instances[instance];
+    instances = FCKeditorAPI.__Instances;
+  }
+  for (var instanceName in instances) {
+    // Shut down the instance and give plugins a chance to detach by changing to a custom status.
+    instances[instanceName].SetStatus(Drupal.wysiwyg.editor.instance.fckeditor.FCK_STATUS_WYSIWYG_SHUTDOWN);
+  }
+};
+
+Drupal.wysiwyg.editor.instance.fckeditor = {
+  FCK_STATUS_WYSIWYG_SHUTDOWN : 42, // Custom shutdown status
+  pluginSettings : {},
+  addPlugin: function(plugin, settings, pluginSettings, instance) {
+    if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+      return;
     }
+    // DEBUG.
+    console.log(instance);
+    this.ed = instance;
+
+    if (Drupal.settings.wysiwyg.plugins[instance.wysiwygInputFormat].drupal[plugin].css) {
+      instance.FCKConfig.EditorAreaCSS += ',' + Drupal.settings.wysiwyg.plugins[instance.wysiwygInputFormat].drupal[plugin].css;
+    }
+
+    // Store settings for this plugin instance
+    this.pluginSettings[plugin] = {
+      globalSettings : settings,
+      instanceSettings : pluginSettings
+    };
+ 
+    // @see fckcommands.js, fck_othercommands.js, fckpastewordcommand.js
+    instance.FCKCommands.RegisterCommand(plugin, {
+      // Invoke the plugin's button.
+      Execute: function () {
+        if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+          var data = { format: 'html', node: instance.FCKSelection.GetParentElement() };
+          // @todo This is NOT the same as data.node.
+          data.content = data.node.innerHTML;
+          Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instance.FCK.Name);
+        }
+      },
+
+      // isNode: Return whether the plugin button should be enabled for the
+      // current selection.
+      // @see FCKUnlinkCommand.prototype.GetState()
+      GetState: function () {
+        // Disabled if not in WYSIWYG mode.
+        if (instance.FCK.EditMode != FCK_EDITMODE_WYSIWYG) {
+          return FCK_TRISTATE_DISABLED;
+        }
+
+        var state = instance.FCK.GetNamedCommandState(this.Name);
+        if (state == FCK_TRISTATE_OFF && instance.FCK.EditMode == FCK_EDITMODE_WYSIWYG) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+            var node = instance.FCKSelection.GetSelectedElement();
+            state = Drupal.wysiwyg.plugins[plugin].isNode(node) ? FCK_TRISTATE_ON : FCK_TRISTATE_OFF;
+          }
+        }
+        return state;
+      },
+
+      /**
+       * Return information about the plugin as a name/value array.
+       */
+      Name: plugin
+    });
+
+    // Register the plugin button.
+    // var FCKToolbarButton = function(commandName, label, tooltip, style, sourceView, contextSensitive, icon)
+    instance.FCKToolbarItems.RegisterItem(plugin, new instance.FCKToolbarButton(plugin, settings.iconTitle, settings.iconTitle, null, false, true, settings.icon));
+
+    return;
+
+    // @todo Functionality from TinyMCE that needs to be covered in here as well.
+    tinymce.create('tinymce.plugins.' + plugin, {
+      init: function(ed, url) {
+        // Load custom CSS for editor contents on startup. DONE
+        ed.onInit.add(function() {
+          if (settings.css) {
+            ed.dom.loadCSS(settings.css);
+          }
+        });
+
+        // Attach: Replace plain text with HTML representations. DONE, hackish
+        ed.onBeforeSetContent.add(function(ed, data) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+            data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, ed.id);
+            data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content);
+          }
+        });
+
+        // Detach: Replace HTML representations with plain text. DONE, hackish
+        ed.onGetContent.add(function(ed, data) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+            data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, ed.id);
+          }
+        });
+
+        // isNode: Return whether the plugin button should be enabled for the
+        // current selection. DONE
+        ed.onNodeChange.add(function(ed, command, node) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+            command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node));
+          }
+        });
+      },
+    });
+  },
+
+  openDialog: function(dialog, params) {
+    // @todo implement open dialog
+  },
+
+  closeDialog: function(dialog) {
+    // @todo implement close dialog
+  },
+
+  prepareContent: function(content) {
+    // @todo not needed for FCKeditor?
+    return content;
+  },
+
+  insert: function(content) {
+    var instance = FCKeditorAPI.GetInstance(this.field);
+    // @see FCK.InsertHtml(), FCK.InsertElement()
+    instance.InsertHtml(content);
   }
 };
 
Index: editors/js/fckeditor.config.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/fckeditor.config.js,v
retrieving revision 1.2
diff -u -p -r1.2 fckeditor.config.js
--- editors/js/fckeditor.config.js	1 Feb 2009 05:58:10 -0000	1.2
+++ editors/js/fckeditor.config.js	20 Mar 2009 21:05:04 -0000
@@ -1,17 +1,100 @@
 // $Id: fckeditor.config.js,v 1.2 2009/02/01 05:58:10 sun Exp $
 
+/*
+ * This file is loaded from within FCKeditor's frame
+ */
+Drupal = window.parent.Drupal;
+$ = window.parent.$;
 /**
  * Fetch and provide original editor settings as local variable.
  *
  * FCKeditor does not support to pass complex variable types to the editor.
- *
- * For whatever reason, our custom 'format' property is not available in
- * FCKConfig.format, but in FCKConfig.PageConfig.format instead.
+ * 
+ * Settings set on FCKinstance.Config are temporarily stored in FCKConfig.PageConfig
  */
-var wysiwygSettings = window.parent.Drupal.settings.wysiwyg.configs.fckeditor[FCKConfig.PageConfig.format];
+var wysiwygInputFormat = FCKConfig.PageConfig.wysiwygInputFormat;
+var wysiwygSettings = Drupal.settings.wysiwyg.configs.fckeditor[wysiwygInputFormat];
 
 /**
  * Apply custom Wysiwyg API toolbar for input format.
  */
 FCKConfig.ToolbarSets['Wysiwyg'] = wysiwygSettings.buttons;
 
+/**
+ * Load format-specific settings into FCKConfig so plugins can append stylesheets etc.
+ */
+
+for (var setting in wysiwygSettings) {
+  if (setting == 'buttons') {
+    continue;
+  }
+  FCKConfig[setting] = wysiwygSettings[setting];
+}
+ 
+/**
+ * Register Drupal plugins for this input format.
+ * Parameters to addPlugin are:
+ * - Plugin name
+ * - Format specific plugin settings
+ * - General plugin settings
+ * - A reference to this window so the plugins can access FCKConfig etc 
+ */
+for (var plugin in Drupal.settings.wysiwyg.plugins[wysiwygInputFormat].drupal) {
+  Drupal.wysiwyg.editor.instance.fckeditor.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[wysiwygInputFormat].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin], window);
+}
+
+/**
+ * Register event listerner for attaching/detaching all the plugins "as one".
+ */
+FCK.Events.AttachEvent('OnStatusChange', function(editorInstance, newStatus) {
+  switch (newStatus) {
+    case FCK_STATUS_COMPLETE:
+      var currentContent = editorInstance.GetData();
+      var oldContent = currentContent;
+      for (var plugin in Drupal.wysiwyg.editor.instance.fckeditor.pluginSettings) {
+        var pluginSettings = Drupal.wysiwyg.editor.instance.fckeditor.pluginSettings[plugin];
+        if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+          currentContent = Drupal.wysiwyg.plugins[plugin].attach(currentContent, pluginSettings.instanceSettings, editorInstance.Name);
+          currentContent = Drupal.wysiwyg.editor.instance.fckeditor.prepareContent(currentContent);
+        }
+      }
+      if(currentContent != oldContent) {
+        // Asynchronous call!
+        editorInstance.SetData(currentContent);
+      }
+      break;
+    case Drupal.wysiwyg.editor.instance.fckeditor.FCK_STATUS_WYSIWYG_SHUTDOWN:
+      // Parse all placeholders back to wysiwyg tags before shutdown
+      var currentContent = editorInstance.GetData();
+      var oldContent = currentContent;
+      for (var plugin in Drupal.wysiwyg.editor.instance.fckeditor.pluginSettings) {
+        var pluginSettings = Drupal.wysiwyg.editor.instance.fckeditor.pluginSettings[plugin];
+        if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+          currentContent = Drupal.wysiwyg.plugins[plugin].detach(currentContent, pluginSettings.instanceSettings, editorInstance.Name);
+        }
+      }
+      if (currentContent != oldContent) {
+        // Asynchronous call! Wait until editor area is rebuilt, OnAfterSetHTML, then begin killing the editor.
+        editorInstance.SetData(currentContent);
+      }
+      break;
+  }
+});
+
+/**
+ * Register event listener for killing the editor.
+ */
+FCK.Events.AttachEvent('OnAfterSetHTML', function(editorInstance) {
+  if (editorInstance.Status == Drupal.wysiwyg.editor.instance.fckeditor.FCK_STATUS_WYSIWYG_SHUTDOWN) {
+    // All tags are processed, now put the final contents in the textarea-
+    editorInstance.UpdateLinkedField();
+    // All done, but killing the editor inside OnAfterSetHTML is not a good idea so postpone it a bit.
+    setTimeout(function () {
+      var instanceName = editorInstance.Name;
+      $('#' + instanceName).show();
+      $('#' + instanceName + '___Config').remove();
+      delete editorInstance;
+      $('#' + instanceName + '___Frame').remove();
+    }, 1);
+  }
+});
