diff --git a/caption-filter.js b/caption-filter.js
deleted file mode 100644
index 2eb17ae..0000000
--- a/caption-filter.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @file
- * JavaScript integrations between the Caption Filter module and particular
- * WYSIWYG editors. This file also implements Insert module hooks to respond
- * to the insertion of content into a WYSIWYG or textarea.
- */
-(function ($) {
-
-$(document).bind('insertIntoActiveEditor', function(event, options) {
-  if (options['fields']['title'] && Drupal.settings.captionFilter.widgets[options['widgetType']]) {
-    options['content'] = '[caption caption="' + options['fields']['title'] + '"]' + options['content'] + '[/caption]';
-  }
-});
-
-})(jQuery);
diff --git a/caption_filter.module b/caption_filter.module
index 5e3a67c..d992f37 100644
--- a/caption_filter.module
+++ b/caption_filter.module
@@ -11,8 +11,9 @@
  * Implementation of hook_init().
  */
 function caption_filter_init() {
-  drupal_add_css(drupal_get_path('module', 'caption_filter') .'/caption-filter.css');
-  drupal_add_js(drupal_get_path('module', 'caption_filter') .'/caption-filter.js');
+  $path = drupal_get_path('module', 'caption_filter');
+  drupal_add_css($path .'/caption-filter.css');
+  drupal_add_js($path .'/js/caption-filter.js');
 }
 
 /**
@@ -174,6 +175,33 @@ function _caption_filter_tag_attributes($text) {
 }
 
 /**
+ * Implements hook_wysiwyg_plugin().
+ *
+ * This hook returns a list of plugins written directly against certain WYSIWYG
+ * editors.
+ */
+function caption_filter_wysiwyg_plugin($editor, $version) {
+  $plugins = array();
+  if ($editor == 'tinymce') {
+    $plugins['captionfilter'] = array(
+      'url' => 'http://drupal.org/project/caption_filter',
+      'path' => drupal_get_path('module', 'caption_filter') . '/js',
+      'filename' => 'caption-filter-tinymce.js',
+      // Caption Filter doesn't actually provide a button, but this code is
+      // needed to make it so that WYSIWYG module will load our plugin.
+      'buttons' => array(
+        'captionfilter' => t('Caption Filter'),
+      ),
+      'options' => array(
+        'captionfilter_css' => base_path() . drupal_get_path('module', 'caption_filter') . '/caption-filter.css',
+      ),
+      'load' => TRUE,
+    );
+  }
+  return $plugins;
+}
+
+/**
  * Implements hook_element_info().
  */
 function caption_filter_elements() {
@@ -200,7 +228,6 @@ function caption_filter_elements() {
  */
 function caption_filter_element_process($element) {
   static $js_added;
-dsm($element);
 
   $field = content_fields($element['#field_name'], $element['#type_name']);
 
diff --git a/js/caption-filter-tinymce.js b/js/caption-filter-tinymce.js
new file mode 100644
index 0000000..8e26f7b
--- /dev/null
+++ b/js/caption-filter-tinymce.js
@@ -0,0 +1,152 @@
+/**
+ * @file
+ * Functionality for aligning images with captions in tinyMCE.
+ *
+ * This file heavily based off of the WordPress "wpEditImage" plugin.
+ * @see http://core.svn.wordpress.org/branches/3.2/wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin.dev.js
+ *
+ * Additionally some code is based off the general WordPress editor.js file:
+ * @see http://core.svn.wordpress.org/branches/3.2/wp-admin/js/editor.dev.js
+ */
+(function() {
+  tinymce.create('tinymce.plugins.CaptionFilter', {
+
+    init : function(ed, url) {
+      var t = this;
+      t.url = url;
+
+      function _do_filter(ed, o) {
+        o.content = Drupal.captionFilter.toHTML(o.content, 'tinymce');
+      };
+
+      // Load custom CSS for editor contents on startup.
+      ed.onInit.add(function() {
+        // Load custom CSS for editor contents on startup.
+        var cssFile = ed.getParam('captionfilter_css', '');
+        if (cssFile) {
+          ed.dom.loadCSS(cssFile);
+        }
+      });
+
+      // Resize the caption wrapper when the image is soft-resized by the user.
+      ed.onMouseUp.add(function(ed, e) {
+        // Webkit (Safari/Chrome) and Opera currently don't have resize handles.
+        if (tinymce.isWebKit || tinymce.isOpera)
+          return;
+
+        var img = ed.selection.getNode();
+        if (img.nodeName == 'IMG') {
+          window.setTimeout(function(){
+            var width;
+            var captionInner = ed.dom.getParent(img, 'div.caption-inner');
+
+            width = ed.dom.getAttrib(img, 'width') || img.width;
+            width = parseInt(width, 10);
+
+            if (captionInner && width != (parseInt(ed.dom.getStyle(captionInner, 'width'), 10))) {
+              ed.dom.setStyle(captionInner, 'width', width);
+              ed.execCommand('mceRepaint');
+            }
+          }, 100);
+        }
+      });
+
+      // When pressing Return inside a caption move the cursor to a new parapraph under it.
+      ed.onKeyPress.add(function(ed, e) {
+        var n, captionInner, captionWrapper, P;
+
+        if (e.keyCode == 13 && !e.shiftKey) {
+          n = ed.selection.getNode();
+          captionInner = ed.dom.getParent(n, 'div.caption-inner');
+          captionWrapper = ed.dom.getParent(captionInner, 'div.caption');
+
+          if (captionInner && captionWrapper) {
+            P = ed.dom.create('p', {}, '&nbsp;');
+            ed.dom.insertAfter(P, captionWrapper);
+
+            if (P.firstChild)
+              ed.selection.select(P.firstChild);
+            else
+              ed.selection.select(P);
+            tinymce.dom.Event.cancel(e);
+            return false;
+          }
+        }
+        // When pressing the Backspace key (Delete on Macs), don't go backwards
+        // into the caption area.
+        else if (e.keyCode == 8) {
+          n = ed.selection.getNode();
+          var previousNode = n.previousSibling;
+          if (previousNode && previousNode.nodeName == 'DIV' && ed.dom.hasClass(previousNode, 'caption')) {
+            // focusOffset is supported by all modern browsers (IE9+). Users of
+            // older browsers will be able to delete into the caption.
+            if (typeof(ed.selection.getSel().focusOffset) == 'number' && ed.selection.getSel().focusOffset == 0) {
+              tinymce.dom.Event.cancel(e);
+              return false;
+            }
+          }
+        }
+      });
+
+      // Set up a handler to float the caption tag instead of the image when
+      // left or right aligning. Also allow the "Cleanup" button to remove
+      // alignment entirely from the caption.
+      ed.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) {
+        if ('JustifyLeft' == cmd || 'JustifyRight' == cmd || 'JustifyCenter' == cmd || 'RemoveFormat' == cmd) {
+          var n, align, wrapperClass, captionWrapper;
+          n = ed.selection.getNode();
+
+          if (n.nodeName == 'IMG') {
+            captionWrapper = ed.dom.getParent(n, 'div.caption');
+
+            if (captionWrapper) {
+              // Remove the existing alignment class.
+              captionWrapper.className = captionWrapper.className.replace(/caption-(left|center|right)\s?/g, '');
+              // Add a new class based on the new alignment (if needed).
+              if ('RemoveFormat' != cmd) {
+                align = cmd.substr(7).toLowerCase();
+                wrapperClass = 'caption-' + align;
+                ed.dom.addClass(captionWrapper, wrapperClass);
+  
+                if (align == 'center')
+                  ed.dom.addClass(captionWrapper, 'mceIEcenter');
+                else
+                  ed.dom.removeClass(captionWrapper, 'mceIEcenter');
+
+                // Stop the image itself from being affected.
+                o.terminate = true;
+              }
+
+              ed.execCommand('mceRepaint');
+            }
+          }
+        }
+      });
+
+      // Set up a handler for the entire editor. Used on initialization.
+      ed.onBeforeSetContent.add(_do_filter);
+
+      // Set up another handler for when a module uses mceSetContent, which only
+      // applies to the current selection, not the entire editor.
+      ed.onBeforeSetContent.add(function(ed, o) {
+        ed.selection.onBeforeSetContent.add(_do_filter);
+      });
+
+      ed.onPostProcess.add(function(ed, o) {
+        o.content = Drupal.captionFilter.toTag(o.content);
+      });
+    },
+
+    getInfo : function() {
+      return {
+        longname : 'Caption Filter',
+        author : 'Nathan Haug',
+        authorurl : 'http://quicksketch.org',
+        infourl : '',
+        version : "1.0"
+      };
+    }
+  });
+
+  tinymce.PluginManager.add('captionfilter', tinymce.plugins.CaptionFilter);
+})();
diff --git a/js/caption-filter.js b/js/caption-filter.js
new file mode 100644
index 0000000..5e1bc8b
--- /dev/null
+++ b/js/caption-filter.js
@@ -0,0 +1,52 @@
+/**
+ * @file
+ * JavaScript integrations between the Caption Filter module and particular
+ * WYSIWYG editors. This file also implements Insert module hooks to respond
+ * to the insertion of content into a WYSIWYG or textarea.
+ */
+(function ($) {
+
+$(document).bind('insertIntoActiveEditor', function(event, options) {
+  if (options['fields']['title'] && Drupal.settings.captionFilter.widgets[options['widgetType']]) {
+    options['content'] = '[caption]' + options['content'] + options['fields']['title'] + '[/caption]';
+  }
+});
+
+Drupal.captionFilter = Drupal.captionFilter || {};
+
+Drupal.captionFilter.toHTML = function(co, editor) {
+  return co.replace(/(?:<p>)?\[caption([^\]]*)\]([\s\S]+?)\[\/caption\](?:<\/p>)?[\s\u00a0]*/g, function(a,b,c){
+    var id, cls, w, tempClass;
+
+    b = b.replace(/\\'|\\&#39;|\\&#039;/g, '&#39;').replace(/\\"|\\&quot;/g, '&quot;');
+    c = c.replace(/\\&#39;|\\&#039;/g, '&#39;').replace(/\\&quot;/g, '&quot;');
+    id = b.match(/id=['"]([^'"]+)/i);
+    cls = b.match(/align=['"]([^'"]+)/i);
+    w = c.match(/width=['"]([0-9]+)/);
+
+    id = ( id && id[1] ) ? id[1] : '';
+    cls = ( cls && cls[1] ) ? 'caption-' + cls[1] : '';
+    w = ( w && w[1] ) ? w[1] : '';
+
+    if (editor == 'tinymce')
+      tempClass = (cls == 'caption-center') ? 'mceTemp mceIEcenter' : 'mceTemp';
+    else if (editor == 'ckeditor')
+      tempClass = (cls == 'caption-center') ? 'mceTemp mceIEcenter' : 'mceTemp';
+    else
+      tempClass = '';
+
+    return '<div class="caption ' + cls + ' ' + tempClass + ' draggable"><div class="caption-inner" style="width: '+(parseInt(w))+'px">' + c + '</div></div>';
+  });
+};
+
+Drupal.captionFilter.toTag = function(co) {
+  return co.replace(/(<div class="caption [^"]*">)\s*<div[^>]+>(.+?)<\/div>\s*<\/div>\s*/gi, function(match, captionWrapper, contents) {
+    var align;
+    align = captionWrapper.match(/class=.*?caption-(left|center|right)/i);
+    align = (align && align[1]) ? align[1] : '';
+
+    return '[caption' + (align ? (' align="' + align + '"') : '') + ']' + contents + '[/caption]';
+  });
+};
+
+})(jQuery);
