 core/modules/edit/edit.module                      |    1 +
 core/modules/edit/js/createjs/editable.js          |    6 +-
 .../createjs/editingWidgets/drupalwysiwygwidget.js |  152 ++++++++++++++++++++
 core/modules/edit/lib/Drupal/edit/EditBundle.php   |    6 +-
 .../edit/lib/Drupal/edit/EditorSelector.php        |   95 ++----------
 .../lib/Drupal/edit/MetadataGeneratorInterface.php |    2 +-
 .../Drupal/edit/Plugin/ProcessedTextEditorBase.php |   26 ----
 .../edit/Plugin/ProcessedTextEditorInterface.php   |   35 -----
 .../edit/Plugin/ProcessedTextEditorManager.php     |   31 ----
 .../lib/Drupal/edit/Tests/EditorSelectionTest.php  |   26 ++--
 .../edit_test/Plugin/editor/editor/Unicorn.php     |   59 ++++++++
 11 files changed, 245 insertions(+), 194 deletions(-)

diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index e3beec0..5a42ab2 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -106,6 +106,7 @@ function edit_library_info() {
       $path . '/js/createjs/storage.js' => $options,
       $path . '/js/createjs/editingWidgets/formwidget.js' => $options,
       $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => $options,
+      $path . '/js/createjs/editingWidgets/drupalwysiwygwidget.js' => $options,
       // Other.
       $path . '/js/util.js' => $options,
       $path . '/js/theme.js' => $options,
diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js
index aac1ed2..15bc122 100644
--- a/core/modules/edit/js/createjs/editable.js
+++ b/core/modules/edit/js/createjs/editable.js
@@ -2,7 +2,7 @@
  * @file
  * Determines which editor to use based on a class attribute.
  */
-(function (jQuery, drupalSettings) {
+(function (jQuery) {
 
 "use strict";
 
@@ -18,7 +18,7 @@
         options: {}
       };
       this.options.editors['direct-with-wysiwyg'] = {
-        widget: drupalSettings.edit.wysiwygEditorWidgetName,
+        widget: 'drupalWysiwygWidget',
         options: {}
       };
       this.options.editors.form = {
@@ -40,4 +40,4 @@
     }
   });
 
-})(jQuery, drupalSettings);
+})(jQuery);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalwysiwygwidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalwysiwygwidget.js
new file mode 100644
index 0000000..a3ff03e
--- /dev/null
+++ b/core/modules/edit/js/createjs/editingWidgets/drupalwysiwygwidget.js
@@ -0,0 +1,152 @@
+/**
+ * @file
+ * Text editor-based Create.js widget for processed text content in Drupal.
+ *
+ * Depends on Editor.module. Works with any (WYSIWYG) editor that implements the
+ * attachTrueWysiwyg(), detach() and onChange() methods.
+ */
+(function (jQuery, Drupal, drupalSettings) {
+
+"use strict";
+
+  jQuery.widget('Drupal.drupalWysiwygWidget', jQuery.Create.editWidget, {
+
+    originalTransformedContent: null,
+
+    textFormat: null,
+
+    textEditor: null,
+
+    /**
+     * Implements jQuery UI widget factory's _init() method.
+     *
+     * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
+     * Get rid of this once that issue is solved.
+     */
+    _init: function() {},
+
+    /**
+     * Implements Create's _initialize() method.
+     */
+    _initialize: function() {
+      var formatID = this.options.widget.element.attr('data-edit-text-format');
+      this.textFormat = drupalSettings.editor.formats[formatID];
+      this.textEditor = Drupal.editors[this.textFormat.editor];
+
+      this._bindEvents();
+    },
+
+    /**
+     * Binds to events.
+     */
+    _bindEvents: function() {
+      var that = this;
+
+      // Sets the state to 'activated' upon clicking the element.
+      this.element.on('click.edit', function(event) {
+        event.stopPropagation();
+        event.preventDefault();
+        that.options.activating();
+      });
+    },
+
+    /**
+     * Makes this PropertyEditor widget react to state changes.
+     */
+    stateChange: function(from, to) {
+      var that = this;
+      switch (to) {
+        case 'inactive':
+          break;
+        case 'candidate':
+          if (from !== 'inactive') {
+            if (from !== 'highlighted') {
+              this.element.attr('contentEditable', 'false');
+              this.textEditor.detach(this.element.get(0), this.textFormat);
+            }
+
+            this._removeValidationErrors();
+            this._cleanUp();
+            this._bindEvents();
+          }
+          break;
+        case 'highlighted':
+          break;
+        case 'activating':
+          // When transformation filters have been been applied to the processed
+          // text of this field, then we'll need to load a re-rendered version of
+          // it without the transformation filters.
+          if (this.options.widget.element.hasClass('edit-text-with-transformation-filters')) {
+            this.originalTransformedContent = this.element.html();
+
+            Drupal.edit.util.loadRerenderedProcessedText({
+              $editorElement: this.element,
+              propertyID: Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property),
+              callback: function (rerendered) {
+                that.element.html(rerendered);
+                that.options.activated();
+              }
+            });
+          }
+          // When no transformation filters have been applied: start WYSIWYG
+          // editing immediately!
+          else {
+            this.options.activated();
+          }
+          break;
+        case 'active':
+          this.element.attr('contentEditable', 'true');
+          this.textEditor.attachTrueWysiwyg(
+            this.element.get(0),
+            this.textFormat,
+            this.toolbarView.getMainWysiwygToolgroupId(),
+            this.toolbarView.getFloatingWysiwygToolgroupId()
+          );
+
+          // Sets the state to 'changed' whenever the content has changed.
+          this.textEditor.onChange(this.element.get(0), function (html) {
+            that.options.changed(html);
+          });
+          break;
+        case 'changed':
+          break;
+        case 'saving':
+          this._removeValidationErrors();
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Removes validation errors' markup changes, if any.
+     *
+     * Note: this only needs to happen for type=direct, because for type=direct,
+     * the property DOM element itself is modified; this is not the case for
+     * type=form.
+     */
+    _removeValidationErrors: function() {
+      this.element
+        .removeClass('edit-validation-error')
+        .next('.edit-validation-errors').remove();
+    },
+
+    /**
+     * Cleans up after the widget has been saved.
+     *
+     * Note: this is where the Create.Storage and accompanying Backbone.sync
+     * abstractions "leak" implementation details. That is only the case because
+     * we have to use Drupal's Form API as a transport mechanism. It is
+     * unfortunately a stateful transport mechanism, and that's why we have to
+     * clean it up here. This clean-up is only necessary when canceling the
+     * editing of a property after having attempted to save at least once.
+     */
+    _cleanUp: function() {
+      Drupal.edit.util.form.unajaxifySaving(jQuery('#edit_backstage form .edit-form-submit'));
+      jQuery('#edit_backstage form').remove();
+    }
+  });
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/lib/Drupal/edit/EditBundle.php b/core/modules/edit/lib/Drupal/edit/EditBundle.php
index d92fd73..26406c7 100644
--- a/core/modules/edit/lib/Drupal/edit/EditBundle.php
+++ b/core/modules/edit/lib/Drupal/edit/EditBundle.php
@@ -20,14 +20,10 @@ class EditBundle extends Bundle {
    * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
    */
   public function build(ContainerBuilder $container) {
-    // Register the plugin managers for our plugin types with the dependency injection container.
-    $container->register('plugin.manager.edit.processed_text_editor', 'Drupal\edit\Plugin\ProcessedTextEditorManager');
-
     $container->register('access_check.edit.entity_field', 'Drupal\edit\Access\EditEntityFieldAccessCheck')
       ->addTag('access_check');
 
-    $container->register('edit.editor.selector', 'Drupal\edit\EditorSelector')
-      ->addArgument(new Reference('plugin.manager.edit.processed_text_editor'));
+    $container->register('edit.editor.selector', 'Drupal\edit\EditorSelector');
 
     $container->register('edit.metadata.generator', 'Drupal\edit\MetadataGenerator')
       ->addArgument(new Reference('access_check.edit.entity_field'))
diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelector.php b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
index 5c44954..b9f501d 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorSelector.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
@@ -16,30 +16,6 @@
 class EditorSelector implements EditorSelectorInterface {
 
   /**
-   * The manager for processed text editor plugins.
-   *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
-   */
-  protected $processedTextEditorManager;
-
-  /**
-   * The processed text editor plugin selected.
-   *
-   * @var \Drupal\edit\Plugin\ProcessedTextEditorInterface
-   */
-  protected $processedTextEditorPlugin;
-
-  /**
-   * Constructs a new EditorSelector.
-   *
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $processed_text_editor_manager
-   *   The manager for processed text editor plugins.
-   */
-  public function __construct(PluginManagerInterface $processed_text_editor_manager) {
-    $this->processedTextEditorManager = $processed_text_editor_manager;
-  }
-
-  /**
    * Implements \Drupal\edit\EditorSelectorInterface::getEditor().
    */
   public function getEditor($formatter_type, FieldInstance $instance, array $items) {
@@ -77,8 +53,7 @@ public function getEditor($formatter_type, FieldInstance $instance, array $items
       elseif (!empty($instance['settings']['text_processing'])) {
         $format_id = $items[0]['format'];
         if (isset($format_id)) {
-          $wysiwyg_plugin = $this->getProcessedTextEditorPlugin();
-          if (isset($wysiwyg_plugin) && $wysiwyg_plugin->checkFormatCompatibility($format_id)) {
+          if (editor_load($format_id)) {
             // Yay! Even though the text is processed, there's a WYSIWYG editor
             // that can work with it.
             $editor = 'direct-with-wysiwyg';
@@ -102,66 +77,24 @@ public function getEditor($formatter_type, FieldInstance $instance, array $items
 
   /**
    * Implements \Drupal\edit\EditorSelectorInterface::getAllEditorAttachments().
+   *
+   * @todo Get rid of the tight coupling with Editor.
    */
   public function getAllEditorAttachments() {
-    $this->getProcessedTextEditorPlugin();
-    if (!isset($this->processedTextEditorPlugin)) {
-      return array();
-    }
-
-    $js = array();
-
-    // Add library and settings for the selected processed text editor plugin.
-    $definition = $this->processedTextEditorPlugin->getDefinition();
-    if (!empty($definition['library'])) {
-      $js['library'][] = array($definition['library']['module'], $definition['library']['name']);
-    }
-    $this->processedTextEditorPlugin->addJsSettings();
+    global $user;
 
-    // Also add the setting to register it with Create.js
-    if (!empty($definition['propertyEditorName'])) {
-      $js['js'][] = array(
-        'data' => array(
-          'edit' => array(
-            'wysiwygEditorWidgetName' => $definition['propertyEditorName'],
-          ),
-        ),
-        'type' => 'setting'
-      );
-    }
-
-    return $js;
-  }
+    $user_formats = filter_formats($user);
+    $definitions = drupal_container()->get('plugin.manager.editor')->getDefinitions();
 
-  /**
-   * Returns the plugin to use for the 'direct-with-wysiwyg' editor.
-   *
-   * @return \Drupal\edit\Plugin\ProcessedTextEditorInterface
-   *   The editor plugin.
-   *
-   * @todo We currently only support one plugin (the first one returned by the
-   *   manager) for the 'direct-with-wysiwyg' editor on any given page. Enhance
-   *   this to allow different ones per element (e.g., Aloha for one text field
-   *   and CKEditor for another one).
-   *
-   * @todo The terminology here is confusing. 'direct-with-wysiwyg' is one of
-   *   several possible "editor"s for processed text. When using it, we need to
-   *   integrate a particular WYSIWYG editor, which in Create.js is called a
-   *   "PropertyEditor widget", but we're not yet including "widget" in the name
-   *   of ProcessedTextEditorInterface to minimize confusion with Field API
-   *   widgets. So, we're currently refering to these as "plugins", which is
-   *   correct in that it's using Drupal's Plugin API, but less informative than
-   *   naming it "widget" or similar.
-   */
-  protected function getProcessedTextEditorPlugin() {
-    if (!isset($this->processedTextEditorPlugin)) {
-      $definitions = $this->processedTextEditorManager->getDefinitions();
-      if (count($definitions)) {
-        $plugin_ids = array_keys($definitions);
-        $plugin_id = $plugin_ids[0];
-        $this->processedTextEditorPlugin = $this->processedTextEditorManager->createInstance($plugin_id);
+    // Filter the current user's text to those that support "true WYSIWYG".
+    $formats = array();
+    foreach ($user_formats as $format_id => $format) {
+      $editor = editor_load($format_id);
+      if ($editor && isset($definitions[$editor->editor]) && $definitions[$editor->editor]['supports_true_wysiwyg'] === 'TRUE') {
+        $formats[$format_id] = $format;
       }
     }
-    return $this->processedTextEditorPlugin;
+
+    return editor_get_attachments($formats);
   }
 }
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
index 9e4fb5d..ff052d7 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
@@ -20,7 +20,7 @@
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity being edited.
-   * @param Drupal\field\FieldInstance $instance
+   * @param \Drupal\field\FieldInstance $instance
    *   The field instance of the field being edited.
    * @param string $langcode
    *   The name of the language for which the field is being edited.
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php b/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php
deleted file mode 100644
index 6e1cace..0000000
--- a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorBase.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of \Drupal\edit\Plugin\ProcessedTextPropertyBase.
- */
-
-namespace Drupal\edit\Plugin;
-
-use Drupal\Component\Plugin\PluginBase;
-
-/**
- * Base class for processed text editor plugins.
- */
-abstract class ProcessedTextEditorBase extends PluginBase implements ProcessedTextEditorInterface {
-
-  /**
-   * Implements \Drupal\edit\Plugin\ProcessedTextEditorInterface::addJsSettings().
-   *
-   * This base class provides an empty implementation for text editors that
-   * do not need to add JavaScript settings besides those added by the library.
-   */
-  public function addJsSettings() {
-  }
-
-}
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorInterface.php b/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorInterface.php
deleted file mode 100644
index 1b2efb1..0000000
--- a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorInterface.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of \Drupal\edit\Plugin\ProcessedTextEditorInterface.
- */
-
-namespace Drupal\edit\Plugin;
-
-use Drupal\Component\Plugin\PluginInspectionInterface;
-
-/**
- * Defines an interface for PropertyEditor widgets for processed text fields.
- *
- * A PropertyEditor widget is a user-facing interface to edit an entity property
- * through Create.js.
- */
-interface ProcessedTextEditorInterface extends PluginInspectionInterface {
-
-  /**
-   * Adds JavaScript settings.
-   */
-  public function addJsSettings();
-
-  /**
-   * Checks if the text editor is compatible with a given text format.
-   *
-   * @param $format_id
-   *   A text format ID.
-   *
-   * @return bool
-   *   TRUE if it is compatible, FALSE otherwise.
-   */
-  public function checkFormatCompatibility($format_id);
-}
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorManager.php b/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorManager.php
deleted file mode 100644
index 26ee525..0000000
--- a/core/modules/edit/lib/Drupal/edit/Plugin/ProcessedTextEditorManager.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of \Drupal\edit\Plugin\ProcessedTextEditorManager.
- */
-
-namespace Drupal\edit\Plugin;
-
-use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Component\Plugin\Factory\DefaultFactory;
-use Drupal\Core\Plugin\Discovery\AlterDecorator;
-use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
-use Drupal\Core\Plugin\Discovery\CacheDecorator;
-
-/**
- * ProcessedTextEditor manager.
- */
-class ProcessedTextEditorManager extends PluginManagerBase {
-
-  /**
-   * Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
-   */
-  public function __construct() {
-    $this->discovery = new AnnotatedClassDiscovery('edit', 'processed_text_editor');
-    $this->discovery = new AlterDecorator($this->discovery, 'edit_wysiwyg');
-    $this->discovery = new CacheDecorator($this->discovery, 'edit:wysiwyg');
-    $this->factory = new DefaultFactory($this->discovery);
-  }
-
-}
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
index 1aca55d..b092f23 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php
@@ -8,7 +8,6 @@
 namespace Drupal\edit\Tests;
 
 use Drupal\simpletest\DrupalUnitTestBase;
-use Drupal\edit\Plugin\ProcessedTextEditorManager;
 use Drupal\edit\EditorSelector;
 
 /**
@@ -29,7 +28,7 @@ class EditorSelectionTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('system', 'field_test', 'field', 'number', 'text', 'edit', 'edit_test');
+  public static $modules = array('system', 'field_test', 'field', 'number', 'text', 'editor', 'edit', 'edit_test');
 
   public static function getInfo() {
     return array(
@@ -51,12 +50,7 @@ function setUp() {
     // Set default storage backend.
     variable_set('field_storage_default', $this->default_storage);
 
-    // @todo Rather than using the real ProcessedTextEditorManager, which can
-    //   find all text editor plugins in the codebase, create a mock one for
-    //   testing that is populated with only the ones we want to test.
-    $text_editor_manager = new ProcessedTextEditorManager();
-
-    $this->editorSelector = new EditorSelector($text_editor_manager);
+    $this->editorSelector = new EditorSelector();
   }
 
   /**
@@ -173,6 +167,10 @@ function testText() {
    * Tests a textual field, with text processing, with cardinality 1 and >1,
    * always with a ProcessedTextEditor plug-in present, but with varying text
    * format compatibility.
+   *
+   * @todo Move to Editor module.
+   * @todo This test currently fails because it needs entity_create() and
+   * entity_load() to work, which is not true by default in DrupalUnitTestBase.
    */
   function testTextWysiwyg() {
     $field_name = 'field_textarea';
@@ -188,16 +186,20 @@ function testTextWysiwyg() {
       array()
     );
 
-    // ProcessedTextEditor plug-in compatible with the full_html text format.
-    state()->set('edit_test.compatible_format', 'full_html');
+    // Associate editor.
+    entity_create('editor', array(
+      'name' => 'Unicorn WYSIWYG Editor',
+      'format' => 'full_html',
+      'editor' => 'unicorn',
+    ));
 
     // Pretend there is an entity with these items for the field.
     $items = array(array('value' => 'Hello, world!', 'format' => 'filtered_html'));
 
-    // Editor selection with cardinality 1, without compatible text format.
+    // Editor selection w/ cardinality 1, text format w/o associated text editor.
     $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "Without cardinality 1, and the filtered_html text format, the 'form' editor is selected.");
 
-    // Editor selection with cardinality 1, with compatible text format.
+    // Editor selection w/ cardinality 1, text format w/ associated text editor.
     $items[0]['format'] = 'full_html';
     $this->assertEqual('direct-with-wysiwyg', $this->getSelectedEditor($items, $field_name), "With cardinality 1, and the full_html text format, the 'direct-with-wysiwyg' editor is selected.");
 
diff --git a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/editor/editor/Unicorn.php b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/editor/editor/Unicorn.php
new file mode 100644
index 0000000..412640a
--- /dev/null
+++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/editor/editor/Unicorn.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\edit_test\Plugin\editor\editor\Unicorn.
+ */
+
+namespace Drupal\edit_test\Plugin\editor\editor;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+use Drupal\editor\Plugin\EditorInterface;
+
+
+
+/**
+ * Defines a Unicorn-based text editor for Drupal.
+ *
+ * @Plugin(
+ *   id = "unicorn",
+ *   title = @Translation("Unicorn"),
+  *  library = {
+ *     "module" = "system",
+ *     "name" = "jquery"
+ *   },
+ *   supports_true_wysiwyg = "FALSE"
+ * )
+ */
+class Unicorn extends PluginBase implements EditorInterface {
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::defaultSettings().
+   */
+  function defaultSettings() {
+    return array('ponies too' => TRUE);
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::settingsForm().
+   */
+  function settingsForm(array &$form, array &$form_state, Editor $editor) {
+    return array();
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::settingsFormValidate().
+   */
+  function settingsFormValidate(array $form, array &$form_state) { }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::generateJsSettings().
+   */
+  function generateJsSettings(Editor $editor) {
+    return array();
+  }
+
+}
