Index: wysiwyg.init.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.init.js,v
retrieving revision 1.2
diff -u -p -r1.2 wysiwyg.init.js
--- wysiwyg.init.js	30 Nov 2008 17:16:27 -0000	1.2
+++ wysiwyg.init.js	1 Dec 2008 15:17:34 -0000
@@ -2,7 +2,7 @@
 
 Drupal.wysiwyg = Drupal.wysiwyg || { 'instances': {} };
 
-Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {} };
+Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {}, 'instance': {} };
 
 Drupal.wysiwyg.plugins = Drupal.wysiwyg.plugins || {};
 
Index: wysiwyg.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.js,v
retrieving revision 1.4
diff -u -p -r1.4 wysiwyg.js
--- wysiwyg.js	1 Dec 2008 14:14:41 -0000	1.4
+++ wysiwyg.js	1 Dec 2008 15:19:01 -0000
@@ -70,8 +70,14 @@ Drupal.wysiwygAttach = function(context,
   if (typeof Drupal.wysiwyg.editor.attach[params.editor] == 'function') {
     // (Re-)initialize field instance.
     Drupal.wysiwyg.instances[params.field] = {};
+    // Provide editor callbacks for plugins.
+    if (typeof Drupal.wysiwyg.editor.instance[params.editor] == 'object') {
+      Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance[params.editor];
+    }
     // Store new editor name and status for this field.
     Drupal.wysiwyg.instances[params.field].editor = params.editor;
+    // Store this field id, so (external) plugins can use it.
+    Drupal.wysiwyg.activeId = params.field;
     // Attach or update toggle link.
     Drupal.wysiwygAttachToggleLink(context, params);
     // Attach editor, if enabled by default or last state was enabled.
@@ -125,6 +131,7 @@ Drupal.wysiwygAttachToggleLink = functio
       Drupal.wysiwygDetach(context, params);
       // After disabling the editor, re-attach default behaviors.
       Drupal.wysiwyg.editor.attach.none(context, params);
+      Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance.none;
       Drupal.wysiwyg.instances[params.field].editor = 'none';
       $(this).html(Drupal.settings.wysiwyg.enable).blur();
     }
Index: wysiwyg.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.module,v
retrieving revision 1.16
diff -u -p -r1.16 wysiwyg.module
--- wysiwyg.module	1 Dec 2008 14:32:41 -0000	1.16
+++ wysiwyg.module	1 Dec 2008 15:19:14 -0000
@@ -124,10 +124,10 @@ function wysiwyg_process_form(&$form) {
               // Check editor theme (and reset it if not/no longer available).
               $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : ''));
 
+              // Add plugin settings (first) for this input format.
+              wysiwyg_add_plugin_settings($profile);
               // Add profile settings for this input format.
               wysiwyg_add_editor_settings($profile, $theme);
-              // Add plugin settings for this input format.
-              wysiwyg_add_plugin_settings($profile);
             }
 
             // Use a prefix/suffix for a single input format, or attach to input
@@ -309,24 +309,55 @@ function wysiwyg_add_editor_settings($pr
 function wysiwyg_add_plugin_settings($profile) {
   static $plugins_added = array();
   
-  if (!isset($plugins_added[$profile->editor])) {
-    $plugins = array();
-    $editor = wysiwyg_get_editor($profile->editor);
-    // Collect editor plugins provided via hook_wysiwyg_plugin().
-    $info = module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']);
-    // Only keep enabled plugins in this profile.
-    foreach ($info as $plugin => $meta) {
+  // External plugins must only be loaded once.
+  // @todo Actually, each native plugin must not be added twice, but different
+  //   profiles can have different plugin-sets for the same editor.  We should
+  //   check each plugin for each editor.
+  if (isset($plugins_added[$profile->editor])) {
+    return;
+  }
+  
+  $editor = wysiwyg_get_editor($profile->editor);
+  // Assume that this editor does not support neither native external plugins,
+  // nor Drupal plugins if it does not provide a callback.
+  if (isset($editor['plugin settings callback']) && function_exists($editor['plugin settings callback'])) {
+    // Collect native editor plugins provided via hook_wysiwyg_plugin().
+    $plugins = module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']);
+    // Only keep enabled native plugins in this profile.
+    foreach ($plugins as $plugin => $meta) {
       if (!isset($profile->settings['buttons'][$plugin])) {
-        unset($info[$plugin]);
+        unset($plugins[$plugin]);
       }
     }
-
-    if (isset($editor['plugin settings callback']) && function_exists($editor['plugin settings callback'])) {
-      $plugins = $editor['plugin settings callback']($editor, $profile, $info);
+    // Invoke the editor's plugin settings callback, so it can populate the
+    // settings for external plugins with custom, required values.
+    $plugins = $editor['plugin settings callback']($editor, $profile, $plugins);
+
+    drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => array('native' => $plugins)))), 'setting');
+
+    // Collect, load, and add API plugins provided by Drupal modules.
+    if (isset($editor['proxy plugin']) && isset($editor['proxy plugin settings callback']) && function_exists($editor['proxy plugin settings callback'])) {
+      $plugins += $editor['proxy plugin'];
+      $proxy = key($editor['proxy plugin']);
+      $proxy_plugins = array();
+      foreach (wysiwyg_get_all_plugins() as $plugin_name => $meta) {
+        if (isset($profile->settings['buttons'][$proxy][$plugin_name])) {
+          $proxy_plugins[$plugin_name] = $meta;
+          // Load the Drupal plugin's JavaScript.
+          drupal_add_js($meta['js path'] .'/'. $meta['js file']);
+          // Add plugin specific settings.
+          if (isset($meta['settings'])) {
+            drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin_name => $meta['settings'])))), 'setting');
+          }
+        }
+      }
+      // Invoke the editor's proxy plugin settings callback, so it can populate
+      // the settings for Drupal plugins with custom, required values.
+      $proxy_plugins = $editor['proxy plugin settings callback']($editor, $profile, $proxy_plugins);
+  
+      drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => array('drupal' => $proxy_plugins)))), 'setting');
     }
 
-    drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => $plugins))), 'setting');
-
     $plugins_added[$profile->editor] = TRUE;
   }
 }
@@ -387,11 +418,17 @@ function wysiwyg_get_plugins($editor_nam
     if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) {
       $plugins = $editor['plugin callback']($editor);
     }
-    // Load our own plugins.
-    include_once drupal_get_path('module', 'wysiwyg') .'/wysiwyg.plugins.inc';
-  
     // Add editor plugins provided via hook_wysiwyg_plugin().
     $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']));
+    // Add API plugins provided by Drupal modules.
+    // @todo We need to pass the filepath to the plugin icon for Drupal plugins.
+    if (isset($editor['proxy plugin'])) {
+      $plugins += $editor['proxy plugin'];
+      $proxy = key($editor['proxy plugin']);
+      foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) {
+        $plugins[$proxy]['buttons'][$plugin_name] = $info['title'];
+      }
+    }
   }
   return $plugins;
 }
@@ -555,6 +592,42 @@ function wysiwyg_get_all_editors() {
 }
 
 /**
+ * Invoke hook_wysiwyg_plugin() in all modules.
+ */
+function wysiwyg_get_all_plugins() {
+  static $plugins;
+
+  if (isset($plugins)) {
+    return $plugins;
+  }
+
+  $plugins = wysiwyg_load_includes('plugins', 'plugin');
+  foreach ($plugins as $name => $properties) {
+    $plugin = &$plugins[$name];
+    // Fill in required properties.
+    $plugin += array(
+      'title' => $plugin['name'],
+      'vendor url' => '',
+      'js path' => $plugin['path'] . '/' . $plugin['name'],
+      'js file' => $plugin['name'] . '.js',
+      'css path' => $plugin['path'] . '/' . $plugin['name'],
+      'css file' => $plugin['name'] . '.css',
+      'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images',
+      'icon file' => $plugin['name'] . '.png',
+      'dialog path' => $plugin['name'],
+      'dialog settings' => array(),
+      'settings callback' => NULL,
+      'settings form callback' => NULL,
+    );
+    // Check whether library is present.
+    if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) {
+      continue;
+    }
+  }
+  return $plugins;
+}
+
+/**
  * Load include files for wysiwyg implemented by all modules.
  *
  * @param $type
Index: wysiwyg.plugins.inc
===================================================================
RCS file: wysiwyg.plugins.inc
diff -N wysiwyg.plugins.inc
--- wysiwyg.plugins.inc	14 Oct 2008 21:45:07 -0000	1.1
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,23 +0,0 @@
-<?php
-// $Id: wysiwyg.plugins.inc,v 1.1 2008/10/14 21:45:07 sun Exp $
-
-
-/**
- * Implementation of hook_wysiwyg_plugin().
- */
-function wysiwyg_wysiwyg_plugin($editor, $version) {
-  switch ($editor) {
-    case 'tinymce':
-      if ($version < 3) {
-        return array(
-          'wysiwyg' => array(
-            'path' => drupal_get_path('module', 'wysiwyg') .'/plugins/break/editor_plugin.js',
-            'buttons' => array('break' => t('Teaser break')),
-            'url' => 'http://drupal.org/project/wysiwyg',
-          ),
-        );
-      }
-      break;
-  }
-}
-
Index: editors/tinymce.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/tinymce.inc,v
retrieving revision 1.15
diff -u -p -r1.15 tinymce.inc
--- editors/tinymce.inc	17 Nov 2008 00:15:29 -0000	1.15
+++ editors/tinymce.inc	30 Nov 2008 13:32:51 -0000
@@ -33,12 +33,19 @@ function wysiwyg_tinymce_editor() {
     'settings callback' => 'wysiwyg_tinymce_settings',
     'plugin callback' => 'wysiwyg_tinymce_plugins',
     'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings',
+    'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings',
     'versions' => array( // Each version can override global editor properties.
       '2.1' => array(
         // 'include files' => array('tinymce-2.inc'),
         'js files' => array('tinymce-2.js'),
         'css files' => array('tinymce-2.css'),
         'download url' => 'http://sourceforge.net/project/showfiles.php?group_id=103281&package_id=111430&release_id=557383',
+        'proxy plugin' => array(
+          'drupal' => array(
+            'load' => TRUE,
+            'internal' => TRUE,
+          ),
+        ),
       ),
       '3.2' => array(
         // 'include files' => array('tinymce-3.inc'),
@@ -59,6 +66,12 @@ function wysiwyg_tinymce_editor() {
             'files' => array('tiny_mce_src.js'),
           ),
         ),
+        'proxy plugin' => array(
+          'drupal' => array(
+            'load' => TRUE,
+            'internal' => TRUE,
+          ),
+        ),
       ),
     ),
     // Optional properties
@@ -189,7 +202,12 @@ function wysiwyg_tinymce_settings($edito
           }
           // Add internal buttons that also need to be loaded as extension.
           else if ($type == 'buttons' && isset($plugins[$plugin]['load'])) {
-            $init['extensions'][$plugin] = 1;
+            if ($plugin == 'drupal') {
+              $init['extensions'][$button] = 1;
+            }
+            else {
+              $init['extensions'][$plugin] = 1;
+            }
           }
           // Add plain extensions.
           else if ($type == 'extensions') {
@@ -299,10 +317,10 @@ function wysiwyg_tinymce_themes($editor,
 }
 
 /**
- * Build a JS settings array of external plugins that need to be loaded separately.
+ * Build a JS settings array of native external plugins that need to be loaded separately.
  *
  * TinyMCE requires that external plugins (i.e. not residing in the editor's
- * directory) are loaded (once) after the editor has been initialized.
+ * directory) are loaded (once) upon initializing the editor.
  */
 function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) {
   $settings = array();
@@ -315,6 +333,23 @@ function wysiwyg_tinymce_plugin_settings
 }
 
 /**
+ * Build a JS settings array for Drupal plugins loaded via the proxy plugin.
+ */
+function wysiwyg_tinymce_proxy_plugin_settings($editor, $profile, $plugins) {
+  $settings = array();
+  foreach ($plugins as $name => $plugin) {
+    $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;
+}
+
+/**
  * Add or remove leading hiven to/of external plugin names.
  *
  * TinyMCE requires that external plugins, which should not be loaded from
Index: editors/js/none.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/none.js,v
retrieving revision 1.3
diff -u -p -r1.3 none.js
--- editors/js/none.js	28 Oct 2008 22:46:05 -0000	1.3
+++ editors/js/none.js	29 Oct 2008 12:21:34 -0000
@@ -43,3 +43,30 @@ Drupal.wysiwyg.editor.detach.none = func
   }
 };
 
+/**
+ * Instance methods for plain text areas.
+ */
+Drupal.wysiwyg.editor.instance.none = {
+  insert: function(instanceId, content) {
+    var editor = document.getElementById(instanceId);
+
+    // IE support.
+    if (document.selection) {
+      editor.focus();
+      sel = document.selection.createRange();
+      sel.text = content;
+    }
+
+    // Mozilla/Firefox/Netscape 7+ support.
+    else if (editor.selectionStart || editor.selectionStart == '0') {
+      var startPos = editor.selectionStart;
+      var endPos = editor.selectionEnd;
+      editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length);
+    }
+
+    // Fallback, just add to the end of the content.
+    else {
+      editor.value += content;
+    }
+  }
+};
Index: editors/js/tinymce-2.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/tinymce-2.js,v
retrieving revision 1.7
diff -u -p -r1.7 tinymce-2.js
--- editors/js/tinymce-2.js	1 Dec 2008 14:14:41 -0000	1.7
+++ editors/js/tinymce-2.js	1 Dec 2008 15:17:35 -0000
@@ -21,8 +21,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun
   for (var format in settings) {
     tinyMCE.init(settings[format]);
   }
-  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce) {
-    tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce[plugin]);
+  // Load native external plugins.
+  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.native) {
+    tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.native[plugin]);
+  }
+  // Load Drupal plugins.
+  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.drupal) {
+    Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
   }
 };
 
@@ -56,3 +61,123 @@ Drupal.wysiwyg.editor.detach.tinymce = f
 //  }
 };
 
+Drupal.wysiwyg.editor.instance.tinymce = {
+  addPlugin: function(plugin, settings, pluginSettings) {
+    // Register plugin.
+    tinyMCE.addPlugin(plugin, {
+
+      // Register an editor command for this plugin, invoked by the plugin's button.
+      execCommand: function(editor_id, element, command, user_interface, value) {
+        switch (command) {
+          case plugin:
+            if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+              var ed = tinyMCE.getInstanceById(editor_id);
+              var data = { format: 'html', node: ed.getFocusElement(), content: ed.getFocusElement() };
+              Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, editor_id);
+              return true;
+            }
+        }
+        // Pass to next handler in chain.
+        return false;
+      },
+
+      // Register the plugin button.
+      getControlHTML: function(control_name) {
+        switch (control_name) {
+          case plugin:
+            return tinyMCE.getButtonHTML(control_name, settings.iconTitle, settings.icon, plugin);
+        }
+        return '';
+      },
+
+      // Load custom CSS for editor contents on startup.
+      initInstance: function(ed) {
+        if (settings.css) {
+          tinyMCE.importCSS(ed.getDoc(), settings.css);
+        }
+      },
+
+      cleanup: function(type, content) {
+        switch (type) {
+          case 'insert_to_editor':
+            // Attach: Replace plain text with HTML representations.
+            if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
+              content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
+              content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(content);
+            }
+            break;
+
+          case 'get_from_editor':
+            // Detach: Replace HTML representations with plain text.
+            if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
+              content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
+            }
+            break;
+        }
+        // Pass through to next handler in chain
+        return content;
+      },
+
+      // isNode: Return whether the plugin button should be enabled for the
+      // current selection.
+      handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) {
+        if (node == null) {
+          return;
+        }
+        if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+          if (Drupal.wysiwyg.plugins[plugin].isNode(node)) {
+            tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonSelected');
+            return true;
+          }
+        }
+        tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonNormal');
+        return true;
+      },
+
+      /**
+       * Return information about the plugin as a name/value array.
+       */
+      getInfo: function() {
+        return { longname: settings.title };
+      }
+    });
+  },
+
+  openDialog: function() {
+  },
+
+  closeDialog: function() {
+    tinyMCEPopup.close();
+  },
+
+  prepareContent: function(content) {
+    // Certain content elements need to have additional DOM properties applied
+    // to prevent this editor from highlighting an internal button in addition
+    // to the button of a Drupal plugin.
+    var specialProperties = {
+      img: { name: 'mce_drupal' }
+    };
+    $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+    jQuery.each(specialProperties, function(element, properties) {
+      $content.find(element).each(function() {
+        for (var property in properties) {
+          if (property == 'class') {
+            $(this).addClass(properties[property]);
+          }
+          else {
+            $(this).attr(property, properties[property]);
+          }
+        }
+      });
+    });
+    return $content.html();
+  },
+
+  insert: function(instanceId, content) {
+    content = this.prepareContent(content);
+    var editor = tinyMCE.getInstanceById(instanceId);
+    editor.execCommand('mceInsertContent', false, content);
+    editor.repaint();
+  }
+};
+
Index: editors/js/tinymce-3.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/tinymce-3.js,v
retrieving revision 1.9
diff -u -p -r1.9 tinymce-3.js
--- editors/js/tinymce-3.js	1 Dec 2008 14:14:41 -0000	1.9
+++ editors/js/tinymce-3.js	1 Dec 2008 15:20:38 -0000
@@ -22,8 +22,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun
   for (var format in settings) {
     tinyMCE.init(settings[format]);
   }
-  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce) {
-    tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins.tinymce[plugin]);
+  // Load native external plugins.
+  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.native) {
+    tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins.tinymce.native[plugin]);
+  }
+  // Load Drupal plugins.
+  for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.drupal) {
+    Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
   }
 };
 
@@ -34,11 +39,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun
  */
 Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
   // Configure editor settings for this input format.
-  for (var setting in settings) {
-    tinyMCE.settings[setting] = settings[setting];
-  }
+  var ed = new tinymce.Editor(params.field, settings);
+  // Reset active instance id on any event.
+  ed.onEvent.add(function(ed, e) {
+    Drupal.wysiwyg.activeId = ed.id;
+  });
   // Attach editor.
-  tinyMCE.execCommand('mceAddControl', true, params.field);
+  ed.render();
 };
 
 /**
@@ -64,3 +71,130 @@ Drupal.wysiwyg.editor.detach.tinymce = f
   }
 };
 
+Drupal.wysiwyg.editor.instance.tinymce = {
+  addPlugin: function(plugin, settings, pluginSettings) {
+    if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
+      return;
+    }
+    tinymce.create('tinymce.plugins.' + plugin, {
+      /**
+       * Initialize the plugin, executed after the plugin has been created.
+       *
+       * @param ed
+       *   The tinymce.Editor instance the plugin is initialized in.
+       * @param url
+       *   The absolute URL of the plugin location.
+       */
+      init : function(ed, url) {
+        // Register an editor command for this plugin, invoked by the plugin's button.
+        ed.addCommand(plugin, function() {
+          if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
+            var data = { format: 'html', node: ed.selection.getNode(), content: ed.selection.getContent() };
+            Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.id);
+          }
+          // @todo
+          if (0) {
+            ed.windowManager.open({
+              // @todo Token replacement for passing parameters like editor
+              //   instance id required?
+              file : BASE_URL + 'index.php?q=' + settings.dialogPath,
+              width : settings.width + parseInt(ed.getLang(plugin + '.delta_width', 0)),
+              height : settings.height + parseInt(ed.getLang(plugin + '.delta_height', 0)),
+              inline : settings.inline
+            }, data);
+          }
+        });
+
+        // Register the plugin button.
+        ed.addButton(plugin, {
+          title : settings.iconTitle,
+          cmd : plugin,
+          image : settings.icon
+        });
+
+        // Load custom CSS for editor contents on startup.
+        ed.onInit.add(function() {
+          if (settings.css) {
+            ed.dom.loadCSS(settings.css);
+          }
+        });
+
+        // Attach: Replace plain text with HTML representations.
+        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.
+        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.
+        ed.onNodeChange.add(function(ed, command, node) {
+          if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
+            command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node));
+          }
+        });
+      },
+
+      /**
+       * Return information about the plugin as a name/value array.
+       */
+      getInfo: function() {
+        return { longname: settings.title };
+      }
+    });
+
+    // Register plugin.
+    tinymce.PluginManager.add(plugin, tinymce.plugins[plugin]);
+  },
+
+  openDialog: function(instanceId, dialog, params) {
+    var editor = tinyMCE.get(instanceId);
+    editor.windowManager.open({
+      file: dialog.url,
+      width: dialog.width,
+      height: dialog.height,
+      inline: 1
+    }, params);
+  },
+
+  closeDialog: function() {
+    tinyMCEPopup.close();
+  },
+
+  prepareContent: function(content) {
+    // Certain content elements need to have additional DOM properties applied
+    // to prevent this editor from highlighting an internal button in addition
+    // to the button of a Drupal plugin.
+    var specialProperties = {
+      img: { class: 'mceItem' }
+    };
+    $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+    jQuery.each(specialProperties, function(element, properties) {
+      $content.find(element).each(function() {
+        for (var property in properties) {
+          if (property == 'class') {
+            $(this).addClass(properties[property]);
+          }
+          else {
+            $(this).attr(property, properties[property]);
+          }
+        }
+      });
+    });
+    return $content.html();
+  },
+
+  insert: function(instanceId, content) {
+    content = this.prepareContent(content);
+    tinyMCE.execInstanceCommand(instanceId, 'mceInsertContent', false, content);
+  }
+};
+
Index: plugins/break.inc
===================================================================
RCS file: plugins/break.inc
diff -N plugins/break.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ plugins/break.inc	14 Oct 2008 20:24:17 -0000
@@ -0,0 +1,21 @@
+<?php
+// $Id: break.inc,v 1.1.2.7 2008/10/05 04:05:04 sun Exp $
+
+
+/**
+ * Implementation of hook_wysiwyg_plugin().
+ */
+function wysiwyg_break_plugin() {
+  $plugins = array();
+  $plugins['break'] = array(
+    'title' => t('Teaser break'),
+    'vendor url' => 'http://drupal.org/project/wysiwyg',
+    'icon file' => 'break.gif',
+    'icon title' => t('Separate the teaser and body of this content'),
+    'settings' => array(
+      'path' => wysiwyg_get_path('plugins/break', TRUE),
+    ),
+  );
+  return $plugins;
+}
+
Index: plugins/break/break.js
===================================================================
RCS file: plugins/break/break.js
diff -N plugins/break/break.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ plugins/break/break.js	15 Oct 2008 15:31:45 -0000
@@ -0,0 +1,48 @@
+// $Id: editor_plugin.js 201 2008-02-12 15:56:56Z sun $
+
+Drupal.wysiwyg.plugins.break = {
+
+  // Return whether the passed node belongs to this plugin.
+  isNode: function(node) {
+    return ($(node).is('img.wysiwyg-break'));
+  },
+
+  // Execute the button.
+  invoke: function(data, settings, instanceId) {
+    if (data.format == 'html') {
+      // Prevent duplicating a teaser break.
+      if ($(data.node).is('img.wysiwyg-break')) {
+        return;
+      }
+      var content = this._getPlaceholder(settings);
+    }
+    else {
+    	// Prevent duplicating a teaser break.
+      // @todo data.content is the selection only; needs access to complete content.
+    	if (data.content.match(/<!--break-->/)) {
+        return;
+    	}
+    	var content = '<!--break-->';
+    }
+    if (typeof content != 'undefined') {
+      Drupal.wysiwyg.instances[instanceId].insert(instanceId, content);
+    }
+  },
+
+  // Replace all <!--break--> tags with images.
+  attach: function(content, settings, instanceId) {
+    content = content.replace(/<!--break-->/g, this._getPlaceholder(settings));
+    return content;
+  },
+
+  // Replace images with <!--break--> tags in editor contents upon data.save.
+  detach: function(content, settings, instanceId) {
+    $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
+    $('img.wysiwyg-break', $content).replaceWith('<!--break-->');
+    return $content.html();
+  },
+
+  _getPlaceholder: function (settings) {
+    return '<img src="' + settings.path + '/images/spacer.gif" alt="&lt;--break-&gt;" title="&lt;--break--&gt;" class="wysiwyg-break" />';
+  }
+};
