 core/modules/edit/css/edit.css                     |  419 ++++++++++++
 core/modules/edit/edit.info                        |    6 +
 core/modules/edit/edit.module                      |  365 +++++++++++
 core/modules/edit/images/attention.png             |    6 +
 core/modules/edit/images/close.png                 |   78 +++
 core/modules/edit/images/throbber.gif              |   73 +++
 core/modules/edit/includes/form.inc                |  147 +++++
 core/modules/edit/includes/missing-api.inc         |   43 ++
 core/modules/edit/includes/pages.inc               |  179 +++++
 core/modules/edit/js/ajax.js                       |  124 ++++
 core/modules/edit/js/edit.js                       |  689 ++++++++++++++++++++
 core/modules/edit/js/theme.js                      |  154 +++++
 core/modules/edit/js/ui-editables.js               |  179 +++++
 core/modules/edit/js/ui.js                         |   82 +++
 core/modules/edit/js/util.js                       |   98 +++
 .../field/formatter/TextDefaultFormatter.php       |    3 +
 .../Plugin/field/formatter/TextPlainFormatter.php  |    3 +
 .../formatter/TextSummaryOrTrimmedFormatter.php    |    3 +
 .../field/formatter/TextTrimmedFormatter.php       |    3 +
 19 files changed, 2654 insertions(+)

diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
new file mode 100644
index 0000000..5a99f11
--- /dev/null
+++ b/core/modules/edit/css/edit.css
@@ -0,0 +1,419 @@
+/**
+ * Animations.
+ */
+.edit-animate-invisible {
+  opacity: 0 !important;
+}
+
+.edit-animate-fast {
+-webkit-transition: all .2s ease;
+   -moz-transition: all .2s ease;
+    -ie-transition: all .2s ease;
+     -o-transition: all .2s ease;
+        transition: all .2s ease;
+}
+
+.edit-animate-default {
+  -webkit-transition: all .4s ease;
+     -moz-transition: all .4s ease;
+      -ie-transition: all .4s ease;
+       -o-transition: all .4s ease;
+          transition: all .4s ease;
+}
+
+.edit-animate-slow {
+-webkit-transition: all .6s ease;
+   -moz-transition: all .6s ease;
+    -ie-transition: all .6s ease;
+     -o-transition: all .6s ease;
+        transition: all .6s ease;
+}
+
+.edit-animate-delay-veryfast {
+  -webkit-transition-delay: .05s;
+}
+
+.edit-animate-delay-fast {
+  -webkit-transition-delay: .2s;
+}
+
+.edit-animate-delay-default {
+  -webkit-transition-delay: .4s;
+}
+
+.edit-animate-delay-slow {
+  -webkit-transition-delay: .6s;
+}
+
+.edit-animate-disable-width {
+  -webkit-transition: width 0s;
+}
+
+.edit-animate-only-visibility {
+  -webkit-transition: opacity .2s ease;
+     -moz-transition: opacity .2s ease;
+      -ie-transition: opacity .2s ease;
+       -o-transition: opacity .2s ease;
+          transition: opacity .2s ease;
+  -webkit-transition-delay: 0s;
+}
+
+
+
+
+/**
+ * Edit's bar — inspired by core's toolbar.module & shortcut.module.
+ */
+#editbar,
+#editbar * {
+  border: 0;
+  font-size: 100%;
+  line-height: inherit;
+  list-style: none;
+  margin: 0;
+  outline: 0;
+  padding: 0;
+  text-align: left; /* LTR */
+  vertical-align: baseline;
+}
+#editbar {
+  position: relative;
+  background: #666;
+  color: #ccc;
+  font: normal small "Lucida Grande", Verdana, sans-serif;
+  margin: 0 -20px;
+  padding: 0 20px;
+  -moz-box-shadow: 0 3px 20px #000;
+  -webkit-box-shadow: 0 3px 20px #000;
+  box-shadow: 0 3px 20px #000;
+  z-index: 500;
+}
+#editbar ul {
+  padding: 5px 0 2px 0;
+  height: 28px;
+  line-height: 24px;
+  margin-left:5px; /* LTR */
+}
+#editbar ul li,
+#editbar ul li a {
+  float: left; /* LTR */
+  padding: 0 5px 0 5px;
+  margin-right: 5px; /* LTR */
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+}
+#editbar a {
+  padding: 5px 10px 5px 5px;
+  line-height: 24px;
+  color: #fefefe;
+  font-size: .846em;
+  text-decoration: none;
+}
+#editbar a:focus,
+#editbar a:hover,
+#editbar a.active {
+  color: #fff;
+}
+#editbar ul li a:focus,
+#editbar ul li a:hover,
+#editbar ul li a.active:focus {
+  background: #555;
+}
+#editbar ul li a.active:hover,
+#editbar ul li a.active {
+  background: #000;
+}
+
+
+
+
+/**
+ * Edit mode: overlay + candidate editables + editables being edited.
+ *
+ * Note: every class is prefixed with "edit-" to prevent collisions with modules
+ * or themes. In IPE-specific DOM subtrees, this is not necessary.
+ */
+
+#edit_overlay {
+  position: fixed;
+  z-index: 250;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(255,255,255,.5);
+  top: 0px; /* offset for navbar, modified later */
+  left: 0px;
+}
+
+/* Editable. */
+.edit-editable {
+  z-index: 300;
+  position: relative;
+}
+.edit-editable:focus {
+  outline: none;
+}
+.edit-field.edit-editable,
+.edit-field.edit-type-direct .edit-editable {
+  box-shadow: 0px 0px 1px 1px #4D9DE9;
+}
+
+/* Highlighted (hovered) editable. */
+.edit-editable.edit-highlighted {
+  min-width: 200px; /* TODO: we even need them to be at least fairly wide! */
+}
+.edit-field.edit-editable.edit-highlighted,
+.edit-form.edit-editable.edit-highlighted,
+.edit-field.edit-type-direct .edit-editable.edit-highlighted {
+  box-shadow: 0px 0px 1px 1px #0199FF, 0px 0px 3px 3px rgba(153, 153, 153, .5);
+}
+
+/* Editing (focused) editable. */
+.edit-form.edit-editable.edit-editing,
+.edit-field.edit-type-direct .edit-editable.edit-editing {
+  /* In the latest design, there's no special styling when editing as opposed to
+     just hovering. */
+}
+
+
+
+
+/**
+ * Edit mode: modal.
+ */
+#edit_modal {
+  z-index: 350;
+  position: fixed;
+  top: 40%;
+  left: 40%;
+  box-shadow: 3px 3px 5px #333;
+  background-color: white;
+  border: 1px solid #0199FF;
+  font-family: 'Droid sans', 'Lucida Grande', sans-serif;
+}
+
+#edit_modal .main {
+  font-size: 130%;
+  margin: 25px;
+  padding-left: 40px;
+  background: transparent url('../images/attention.png') no-repeat;
+}
+
+#edit_modal .actions {
+  border-top: 1px solid #ddd;
+  padding: 3px inherit;
+  text-align: right;
+  background: #f5f5f5;
+}
+
+/* Modal active: prevent user from interacting with toolbar & editables. */
+.edit-toolbar-container.edit-belowoverlay {
+  z-index: 210;
+}
+.edit-editable.edit-belowoverlay {
+  z-index: 200;
+}
+
+
+
+
+/**
+ * Edit mode: form.
+ */
+#edit_backstage {
+  display: none;
+}
+
+.edit-form {
+  position: absolute;
+  z-index: 300;
+  box-shadow: 0 0 30px 4px #4F4F4F;
+  max-width: 35em;
+}
+
+.edit-form .placeholder {
+  min-height: 22px;
+}
+
+/* Default form styling overrides. */
+.edit-form form { padding: 1em; }
+.edit-form .form-item { margin: 0; }
+.edit-form .form-wrapper { margin: .5em; }
+.edit-form .form-actions { display: none; }
+.edit-form input { max-width: 100%; }
+
+
+
+
+/**
+ * Edit mode: toolbars
+ */
+
+/* Trick: wrap statically positioned elements in relatively positioned element
+   without changing its location. This allows us to absolutely position the
+   toolbar.
+*/
+.edit-toolbar-container,
+.edit-form-container {
+  position: relative;
+  padding: 0;
+  border: 0;
+  margin: 0;
+  vertical-align: baseline;
+  z-index: 310;
+}
+
+.edit-toolbar-heightfaker {
+  height: auto;
+  position: absolute;
+  bottom: 1px;
+  box-shadow: 0px 0px 1px 1px #0199FF, 0px 0px 3px 3px rgba(153, 153, 153, .5);
+  background: #FFF;
+}
+
+/* The toolbar; these are not necessarily visible. */
+.edit-toolbar {
+  position: relative;
+  height: 100%;
+  font-family: 'Droid sans', 'Lucida Grande', sans-serif;
+}
+.edit-toolbar-heightfaker {
+  clip: rect(-1000px, 1000px, auto, -1000px); /* Remove bottom box-shadow. */
+}
+/* Exception: when used for a directly WYSIWYG editable field that is actively
+   being edited. */
+.edit-type-direct-with-wysiwyg .edit-editing .edit-toolbar-heightfaker {
+  width: 100%;
+  clip: auto;
+}
+
+
+/* The toolbar contains toolgroups; these are visible. */
+.edit-toolgroup {
+  float: left; /* LTR */
+}
+
+/* Info toolgroup. */
+.edit-toolgroup.info {
+  float: left; /* LTR */
+  font-weight: bolder;
+  padding: 0 5px;
+  background: #FFF url('../images/throbber.gif') no-repeat -60px 60px;
+}
+.edit-toolgroup.info.loading {
+  padding-right: 35px;
+  background-position: 90% 50%;
+}
+
+/* Operations toolgroup. */
+.edit-toolgroup.ops {
+  float: right; /* LTR */
+  margin-left: 5px;
+}
+
+.edit-toolgroup.wysiwyg-tabs {
+  float: right;
+}
+.edit-toolgroup.wysiwyg {
+  clear: left;
+  width: 100%;
+  padding-left: none;
+}
+
+
+
+/**
+ * Edit mode: buttons (in both modal and toolbar).
+ */
+#edit_modal a,
+.edit-toolbar a {
+  float: left; /* LTR */
+  display: block;
+  height: 21px;
+  min-width: 21px;
+  padding: 3px 6px 3px 6px;
+  margin: 4px 5px 1px 0;
+  border: 1px solid #fff;
+  border-radius: 3px;
+  color: white;
+  text-decoration: none;
+  font-size: 13px;
+}
+#edit_modal a {
+  float: none;
+  display: inline-block;
+}
+
+#edit_modal a:link,
+#edit_modal a:visited,
+#edit_modal a:hover,
+#edit_modal a:active,
+.edit-toolbar a:link,
+.edit-toolbar a:visited,
+.edit-toolbar a:hover,
+.edit-toolbar a:active {
+  text-decoration: none;
+}
+
+/* Button with icons. */
+#edit_modal a span,
+.edit-toolbar a span {
+  width: 22px;
+  height: 19px;
+  display: block;
+  float: left;
+}
+.edit-toolbar a span.close {
+  background: url('../images/close.png') no-repeat 2px 2px;
+}
+
+.edit-toolbar a span.close:hover {
+  /* TODO: use a different "close" image */
+}
+
+
+.edit-toolbar a.blank-button {
+  color: black;
+}
+
+#edit_modal a.blue-button,
+.edit-toolbar a.blue-button {
+  color: white;
+  background-image: -webkit-linear-gradient(top, #6fc2f2 0%, #4e97c0 100%);
+  background-image: linear-gradient(top, #6fc2f2 0%, #4e97c0 100%);
+  border-radius: 5px;
+}
+
+#edit_modal a.gray-button,
+.edit-toolbar a.gray-button {
+  color: #666;
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #ccc 100%);
+  background-image: linear-gradient(top, #f5f5f5 0%, #ccc 100%);
+  border-radius: 5px;
+}
+
+#edit_modal a img.gray-button.close img, .gray-button.save img, .blue-button.save img,
+.edit-toolbar a img.gray-button.close img, .gray-button.save img, .blue-button.save img {
+  padding: 0;
+}
+
+.gray-button img, .blue-button img,
+.gray-button img, .blue-button img {
+  padding-right: 5px;
+}
+
+#edit_modal a.blue-button:hover,
+.edit-toolbar a.blue-button:hover,
+#edit_modal a.blue-button:active,
+.edit-toolbar a.blue-button:active {
+  border: 1px solid #55a5d3;
+  box-shadow: 0px 2px 1px rgba(0,0,0,0.2);
+}
+
+#edit_modal a.gray-button:hover,
+.edit-toolbar a.gray-button:hover,
+#edit_modal a.gray-button:active,
+.edit-toolbar a.gray-button:active {
+  border: 1px solid #cdcdcd;
+  box-shadow: 0px 2px 1px rgba(0,0,0,0.1);
+}
diff --git a/core/modules/edit/edit.info b/core/modules/edit/edit.info
new file mode 100644
index 0000000..630c825
--- /dev/null
+++ b/core/modules/edit/edit.info
@@ -0,0 +1,6 @@
+name = Edit
+description = In-place content editing.
+package = User interface
+core = 8.x
+
+dependencies[] = field
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
new file mode 100644
index 0000000..29e3ccf
--- /dev/null
+++ b/core/modules/edit/edit.module
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * @file
+ * Provides inline content editing functionality for fields and entities.
+ *
+ * The Edit module makes content editable inline. Rather than having to visit a
+ * separate page to edit content, it may be edited in-place.
+ *
+ * Technically, this module adds classes and data- attributes to fields and
+ * entities, enabling them for in-place editing.
+ */
+
+use Drupal\Core\Template\Attribute;
+use Drupal\field\FieldInstance;
+
+// @todo Get rid of missing-api.inc by improving Entity API.
+module_load_include('inc', 'edit', 'includes/missing-api');
+// @todo Get rid of text.inc once Edit is committed by moving this logic into text.module.
+module_load_include('inc', 'edit', 'includes/text');
+
+/**
+ * Implements hook_menu()
+ */
+function edit_menu() {
+  $items = array();
+  $items['admin/edit/field/%/%/%/%/%'] = array(
+    // Access is controlled after we have inspected the entity, which can't
+    // easily happen until after the callback.
+    'access arguments' => array(TRUE),
+    'access callback'  => TRUE,
+    'page callback'    => 'edit_field_edit',
+    'page arguments'   => array(3, 4, 5, 6, 7),
+    'file'             => 'includes/pages.inc',
+    'delivery callback'=> 'ajax_deliver',
+    'theme callback'   => 'ajax_base_page_theme',
+  );
+  $items['admin/render-without-transformations/field/%/%/%/%/%'] = array(
+    // Access is controlled after we have inspected the entity, which can't
+    // easily happen until after the callback.
+    'access arguments' => array(TRUE),
+    'access callback'  => TRUE,
+    'page callback'    => 'edit_text_field_render_without_transformation_filters',
+    'page arguments'   => array(3, 4, 5, 6, 7),
+    'file'             => 'includes/pages.inc',
+    'delivery callback'=> 'ajax_deliver',
+    'theme callback'   => 'ajax_base_page_theme',
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_page_alter().
+ */
+function edit_page_alter(&$page) {
+  // Don't provide a view/edit toggle on admin pages, node/add and node/%/edit pages.
+  if (arg(0) == 'admin' || (arg(0) == 'node' && arg(1) == 'add') || (arg(0) == 'node' && arg(2) == 'edit')) {
+    return;
+  }
+
+  $page['page_top']['edit'] = array(
+    'view_edit_toggle' => array(
+      '#prefix' => '<div id="editbar"><h2 class="element-invisible">' . t('In-place edit operations') . '</h2><ul id="edit_view-edit-toggles">',
+      '#suffix' => '</ul></div>',
+      'content' => array(
+        array(
+          '#theme' => 'menu_local_task',
+          '#link' => array('title' => t('View'), 'href' => current_path(), 'localized_options' => array('attributes' => array('class' => array('edit_view-edit-toggle', 'edit-view')))),
+          '#active' => TRUE,
+        ),
+        array(
+          '#theme' => 'menu_local_task',
+          '#link' => array('title' => t('Quick edit'), 'href' => '#', 'localized_options' => array('attributes' => array('class' => array('edit_view-edit-toggle', 'edit-edit')))),
+        ),
+      ),
+      '#attached' => array(
+        'library' => array(
+          array('edit', 'edit'),
+        ),
+      ),
+    ),
+    '#post_render' => array(
+      'edit_editbar_post_render',
+    ),
+  );
+}
+
+/**
+ * Post-render function to remove the editbar if nothing editable is present.
+ */
+function edit_editbar_post_render($html) {
+  global $editbar;
+  return ($editbar !== TRUE) ? '' : $html;
+}
+
+/**
+ * Implements hook_library().
+ */
+function edit_library_info() {
+  $path = drupal_get_path('module', 'edit');
+  $libraries['edit'] = array(
+    'title' => 'Edit: in-place editing',
+    'website' => 'http://drupal.org/project/edit',
+    'version' => VERSION,
+    'js' => array(
+      $path . '/js/edit.js' => array(
+        'defer' => TRUE,
+      ),
+      $path . '/js/util.js' => array(
+        'defer' => TRUE,
+      ),
+      $path . '/js/ui.js' => array(
+        'defer' => TRUE,
+      ),
+      $path . '/js/ui-editables.js' => array(
+        'defer' => TRUE,
+      ),
+      $path . '/js/theme.js' => array(
+        'defer' => TRUE,
+      ),
+      $path . '/js/ajax.js' => array(
+        'defer' => TRUE,
+      ),
+      // Basic settings.
+      array(
+        'data' => array('edit' => array(
+          'fieldFormURL' => url('admin/edit/field/!entity_type/!id/!field_name/!langcode/!view_mode'),
+          'rerenderProcessedTextURL' => url('admin/render-without-transformations/field/!entity_type/!id/!field_name/!langcode/!view_mode'),
+          'context' => 'body',
+        )),
+        'type' => 'setting',
+      ),
+    ),
+    'css' => array(
+      $path . '/css/edit.css',
+    ),
+    'dependencies' => array(
+      array('system', 'jquery.form'),
+      array('system', 'drupal.form'),
+      array('system', 'drupal.ajax'),
+    ),
+  );
+
+  // Only add dependencies on the WYSIWYG editor when it's actually available.
+  if (count(module_implements('edit_wysiwyg_info'))) {
+    $libraries['edit']['dependencies'][] = _edit_get_wysiwyg_info('javascript library');
+  }
+
+  return $libraries;
+}
+
+/**
+ * Implements hook_field_attach_view_alter().
+ */
+function edit_field_attach_view_alter(&$output, $context) {
+  // Special case for this special mode.
+  if ($context['display'] == 'edit-render-without-transformation-filters') {
+    $children = element_children($output);
+    $field = reset($children);
+    $langcode = $output[$field]['#language'];
+    foreach (array_keys($output[$field]['#items']) as $item) {
+      $text      = $output[$field]['#items'][$item]['value'];
+      $format_id = $output[$field]['#items'][$item]['format'];
+      $untransformed = check_markup($text, $format_id, $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE));
+      $output[$field][$item]['#markup'] = $untransformed;
+    }
+  }
+}
+
+/**
+ * Implements hook_preprocess_field().
+ */
+function edit_preprocess_field(&$variables) {
+  $entity = $variables['element']['#object'];
+  $name = $variables['element']['#field_name'];
+  $langcode = $variables['element']['#language'];
+  $view_mode = $variables['element']['#view_mode'];
+  $formatter_type = $variables['element']['#formatter'];
+  $field = $entity->{$name}[$langcode];
+  $instance_info = field_info_instance($entity->entityType(), $name, $entity->bundle());
+
+  $entity_access = edit_entity_access('update', $entity->entityType(), $entity);
+  $field_access = field_access('edit', $name, $entity->entityType(), $entity);
+  $editability = _edit_analyze_field_editability($field, $instance_info, $formatter_type);
+  if ($entity_access && $field_access && $editability != 'disabled') {
+    global $editbar;
+    $editbar = TRUE;
+
+    // Mark this field as editable and provide metadata through data- attributes.
+    $variables['attributes']['data-edit-field-label'] = $instance_info->definition['label'];
+    $variables['attributes']['data-edit-id'] = $entity->entityType() . ':' . $entity->id() . ':' . $name . ':' . $langcode . ':' . $view_mode;
+    $variables['attributes']['class'][] = 'edit-field';
+    $variables['attributes']['class'][] = 'edit-allowed';
+    $variables['attributes']['class'][] = 'edit-type-' . $editability;
+    if ($editability == 'direct-with-wysiwyg') {
+      $variables['attributes']['class'][] = 'edit-type-direct';
+      $format_id = $entity->{$name}[$langcode][0]['format'];
+      _edit_preprocess_field_wysiwyg($variables, $format_id);
+    }
+  }
+}
+
+/**
+ * Helper function for edit_preprocess_field().
+ *
+ * @param  array $variables
+ *   @see edit_preprocess_field()
+ * @param  string $format_id
+ *   A text format id.
+ */
+function _edit_preprocess_field_wysiwyg(&$variables, $format_id) {
+  // Let the WYSIWYG editor know the text format.
+  $variables['attributes']['data-edit-text-format'] = $format_id;
+
+  // Ensure the WYSIWYG editor has the necessary text format related
+  // metadata.
+  $settings_callback = _edit_get_wysiwyg_info('javascript settings callback');
+  $settings_callback();
+
+  // Let the JavaScript logic know whether transformation filters are used
+  // in this format, so it can decide whether to re-render the text or not.
+  $filter_types = filter_get_filter_types_by_format($format_id);
+  $transformation_filter_types = array(
+    FILTER_TYPE_TRANSFORM_REVERSIBLE,
+    FILTER_TYPE_TRANSFORM_IRREVERSIBLE
+  );
+  if (count(array_intersect($transformation_filter_types, $filter_types))) {
+    $variables['attributes']['class'][] = 'edit-text-with-transformation-filters';
+  }
+  else {
+    $variables['attributes']['class'][] = 'edit-text-without-transformation-filters';
+  }
+}
+
+/**
+ * Given a field, its instance info and the formatter being used, determine its
+ * editability.
+ *
+ * @param array $field
+ *   The field's field array.
+ * @param FieldInstance $instance_info
+ *   The field's instance info.
+ * @param string $formatter_type
+ *   The field's formatter type name.
+ * @return string
+ *   The editability: 'disabled', 'form', 'direct' or 'direct-with-wysiwyg'.
+ */
+function _edit_analyze_field_editability($field, FieldInstance $instance_info, $formatter_type) {
+  $name = $instance_info->definition['field_name'];
+
+  // If the formatter doesn't contain the edit property, default it to 'form'
+  // editability, which should always work.
+  $formatter_info = field_info_formatter_types($formatter_type);
+  if (empty($formatter_info['edit']['editability'])) {
+    $formatter_info['edit']['editability'] = 'form';
+  }
+
+  $editability = $formatter_info['edit']['editability'];
+
+  // If editing is explicitly disabled for this field, return early to avoid
+  // any further processing.
+  if ($editability == 'disabled') {
+    return;
+  }
+
+  // If directly editable, check the cardinality. If the cardinality is greater
+  // than 1, use a form to edit the field.
+  if ($editability == 'direct') {
+    $info = field_info_field($name);
+    if ($info['cardinality'] != 1) {
+      $editability = 'form';
+    }
+  }
+
+  // If still directly editable, check whether "regular" direct editing (almost
+  // bare contentEditable) editing should be used or WYSIWYG-based direct
+  // editing should be used. In the latter case
+  if ($editability == 'direct') {
+    // If this field is configured to not use text processing; it is plain text
+    // "regular" direct editing should be used, which is already set.
+    // On the other hand, if it is configured to use text processing; then we
+    // must check whether 'direct-with-wysiwyg' or 'form' editability should be
+    // used.
+    if (!empty($instance_info->definition['settings']['text_processing'])) {
+      $format_id = $field[0]['format'];
+      $editability = _edit_wysiwyg_analyze_field_editability($format_id);
+    }
+  }
+
+  return $editability;
+}
+
+/**
+ * Given a text field (with cardinality 1) that defaults to 'direct' editability
+ * and has text processing enabled, check whether the text format allows it to
+ * use WYSIWYG-powered direct editing or whether 'form' based editing needs to
+ * be used.
+ *
+ * @param string|NULL $format_id
+ *   The field's current text format.
+ *
+ * @return string
+ *   The editability: 'direct-with-wysiwyg' or 'form'.
+ */
+function _edit_wysiwyg_analyze_field_editability($format_id = NULL) {
+  // If no format is assigned yet, (e.g. when the field is still empty (NULL)),
+  // then provide form-based editing, so that the user is able to select a text
+  // format. (Direct editing doesn't allow the user to change the format.)
+  if (empty($format_id)) {
+    return 'form';
+  }
+  // If no WYSIWYG editor is available, then fall back to form-based editing.
+  elseif (count(_edit_get_wysiwyg_info()) == 0) {
+    return 'form';
+  }
+  // If the WYSIWYG editor is not compatible with the current format, then fall
+  // back to form-based editing.
+  else {
+    $compatibility_callback = _edit_get_wysiwyg_info('format compatibility callback');
+    if (!$compatibility_callback($format_id)) {
+      return 'form';
+    }
+  }
+
+  return 'direct-with-wysiwyg';
+}
+
+/**
+ * Retrieves a list of all available WYSIWYG integration for Edit. Only the
+ * first is actually used.
+ *
+ * @todo Convert to the plug-in system!
+ *
+ * @param string $key
+ *   The key to get a value for.
+ *
+ * @see hook_edit_wysiwyg_info()
+ * @see hook_edit_wysiwyg_info_alter()
+ */
+function _edit_get_wysiwyg_info($key = NULL) {
+  $edit_wysiwyg_info = &drupal_static(__FUNCTION__, array());
+
+  if (empty($edit_wysiwyg_info)) {
+    $cache = cache()->get('edit_wysiwyg_info');
+    if ($cache === FALSE) {
+      // Rebuild the cache and save it.
+      $edit_wysiwyg_info = module_invoke_all('edit_wysiwyg_info');
+      drupal_alter('edit_wysiwyg_info', $edit_wysiwyg_info);
+    }
+    else {
+      $edit_wysiwyg_info = $cache->data;
+    }
+  }
+
+  if (isset($key)) {
+    $modules = array_keys($edit_wysiwyg_info);
+    $first = $modules[0];
+
+    return $edit_wysiwyg_info[$first][$key];
+  }
+  else {
+    return $edit_wysiwyg_info;
+  }
+}
diff --git a/core/modules/edit/images/attention.png b/core/modules/edit/images/attention.png
new file mode 100644
index 0000000..3c833c9
--- /dev/null
+++ b/core/modules/edit/images/attention.png
@@ -0,0 +1,6 @@
+PNG
+
+   IHDR          Ǎ   tEXtSoftware Adobe ImageReadyqe<  $iTXtXML:com.adobe.xmp     <?xpacket begin="﻿" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CS6 (Macintosh)" xmpMM:InstanceID="xmp.iid:6D93FC30979811E1A7F2C5FBDFE31AF7" xmpMM:DocumentID="xmp.did:6D93FC31979811E1A7F2C5FBDFE31AF7"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:4650FC73972811E1A7F2C5FBDFE31AF7" stRef:documentID="xmp.did:4650FC74972811E1A7F2C5FBDFE31AF7"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>!  	IDATxڼV]HQ.aRcEPP$Rڏd`ѣ[o	`JH$f{:n;θ3zg{{2q(^uYnx2K_B3Y_us۝Uޖ	ai/0p6~s _LyXڜ~=F0p͏dV7>fP,;Ӊ^Nw-ȁ`ޔԜiW1PǫC;5^q	wp}C& Yk{Ao[9܄Àu-`pP:iю	_tU" zAL)DZ0pU4"c2Jo^h'{Rrt6R<H;>K3P	{]<Fے"%آv#LB%#䶁eބSj\Y zTV|+lIUa%
+,4f {euU#nKΓQv
+%lkyN/0	Ӭk%tf:kӶvioI'eTLNEm1b %q(B+`IIV#k,$%$=<f@ k;:Fpb!rEF0
+"tCzȅ9Ï>6㢝2'"ܼCmw{8{%ބ݆?u*q X L    IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/close.png b/core/modules/edit/images/close.png
new file mode 100644
index 0000000..2f5d665
--- /dev/null
+++ b/core/modules/edit/images/close.png
@@ -0,0 +1,78 @@
+PNG
+
+   IHDR         a  
+iTXtXML:com.adobe.xmp     <?xpacket begin="﻿" id="W5M0MpCehiHzreSzNTczkc9d"?>
+<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.0-c060 61.134777, 2010/02/12-17:32:00        ">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+  <rdf:Description rdf:about=""
+    xmlns:xmpRights="http://ns.adobe.com/xap/1.0/rights/"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
+    xmlns:plus_1_="http://ns.useplus.org/ldf/xmp/1.0/"
+    xmlns:xmp="http://ns.adobe.com/xap/1.0/"
+    xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
+    xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
+   xmpRights:Marked="True"
+   xmp:MetadataDate="2011-01-25T13:55:09+01:00"
+   xmpMM:InstanceID="xmp.iid:C4D5F5568228E011989CC0A1AD02B5C2"
+   xmpMM:DocumentID="xmp.did:C4D5F5568228E011989CC0A1AD02B5C2"
+   xmpMM:OriginalDocumentID="xmp.did:C4D5F5568228E011989CC0A1AD02B5C2">
+   <xmpRights:UsageTerms>
+    <rdf:Alt>
+     <rdf:li xml:lang="x-default">Creative Commons Attribution-NonCommercial license</rdf:li>
+    </rdf:Alt>
+   </xmpRights:UsageTerms>
+   <dc:creator>
+    <rdf:Seq>
+     <rdf:li>Gentleface custom toolbar icons design</rdf:li>
+    </rdf:Seq>
+   </dc:creator>
+   <dc:description>
+    <rdf:Alt>
+     <rdf:li xml:lang="x-default">Wireframe mono toolbar icons</rdf:li>
+    </rdf:Alt>
+   </dc:description>
+   <dc:subject>
+    <rdf:Bag>
+     <rdf:li>custom icon design</rdf:li>
+     <rdf:li>toolbar icons</rdf:li>
+     <rdf:li>custom icons</rdf:li>
+     <rdf:li>interface design</rdf:li>
+     <rdf:li>ui design</rdf:li>
+     <rdf:li>gui design</rdf:li>
+     <rdf:li>taskbar icons</rdf:li>
+    </rdf:Bag>
+   </dc:subject>
+   <dc:rights>
+    <rdf:Alt>
+     <rdf:li xml:lang="x-default">Creative Commons Attribution-NonCommercial license</rdf:li>
+    </rdf:Alt>
+   </dc:rights>
+   <Iptc4xmpCore:CreatorContactInfo
+    Iptc4xmpCore:CiUrlWork="http://www.gentleface.com"/>
+   <plus_1_:ImageCreator>
+    <rdf:Seq>
+     <rdf:li
+      plus_1_:ImageCreatorName="gentleface.com"/>
+    </rdf:Seq>
+   </plus_1_:ImageCreator>
+   <plus_1_:CopyrightOwner>
+    <rdf:Seq>
+     <rdf:li
+      plus_1_:CopyrightOwnerName="gentleface.com"/>
+    </rdf:Seq>
+   </plus_1_:CopyrightOwner>
+   <xmpMM:History>
+    <rdf:Seq>
+     <rdf:li
+      stEvt:action="saved"
+      stEvt:instanceID="xmp.iid:C4D5F5568228E011989CC0A1AD02B5C2"
+      stEvt:when="2011-01-25T13:55:09+01:00"
+      stEvt:changed="/metadata"/>
+    </rdf:Seq>
+   </xmpMM:History>
+  </rdf:Description>
+ </rdf:RDF>
+</x:xmpmeta>
+<?xpacket end="r"?>KK   tEXtSoftware Adobe ImageReadyqe<   <tEXtALTTag This is the icon from Gentleface.com free icons set. k   tEXtCopyright ROYALTY FREE LICENSE ًi   EiTXtDescription     This is the icon from Gentleface.com free icons set.    #iTXtCopyright     ROYALTY FREE LICENSE ']
+J  iIDATxڤRK@M"(nq17ě87HN0G!9Bn2ApejܹIIK'&LQ]W/mv}4Ow!rs~$l6o"n@i X}x8bIZfɜnכ,-r*˒Eap6CQuL	VVM</8NQZ_	+	-/X,"4Oq>..c2j:Em	ꈠJͳ@qk$	`өltDkd2a.ÑiBrvxݗqߓFd4/ ^A/`0`޲2S^` K&FS    IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/throbber.gif b/core/modules/edit/images/throbber.gif
new file mode 100644
index 0000000..58f4a42
--- /dev/null
+++ b/core/modules/edit/images/throbber.gif
@@ -0,0 +1,73 @@
+GIF89a    			      !!!"""######$$$%%%%%%&&&&&&&&&''''''''''''''''''''''''(((((()))******,,,...000222444555666888999:::;;;<<<===???AAACCCEEEFFFHHHJJJKKKLLLLLLMMMNNNOOOOOOPPPPPPQQQRRRRRRRRRSSSSSSSSSTTTTTTTTTTTTUUUUUUVVVXXXYYYZZZ\\\\\\]]]]]]^^^^^^______``````bbbdddfffggghhhiiijjjlllnnnooovvv{{{!NETSCAPE2.0   ! 
+   ,        	8P@{%#\-"V	!׾AWhc}(qR2@(	JK>^WK>$T`4U՚h&d`pʘNlxeIZ-(+8jZ+T5r.RI>x_IecʰF&Kr*\5}¤d<MUXJ@g,JnFW(\\qJTKF*.XrJ/Vz)ի>W05U/OT鐒}Պ%+>Kb')E,FՔVS@ ! 
+   ,      									
+
+
+
+
+
+   """"""###$$$&&&(((***,,,...111222333444444555666666777888888999::::::;;;<<<<<<===>>>>>>??????@@@@@@@@@AAABBBBBBCCCCCCDDDDDDEEEEEEGGGHHHJJJLLLOOORRRUUUWWWXXXZZZ[[[]]]______``````aaaaaaaaaaaabbbbbbbbbccccccdddfffggghhhhhhkkkmmmqqqvvvzzz||| 	8pHQ"Ȱ(]!P#ҏB	aGKc[E	rcHx}hR`G?YTERl%F Sz"*№/T.ZΟR49EUY.]%L+Es4UT%O)!(mUSoZV+R"BT˖8qtYį""՘q[uER<aBqU*)%Kd	JmR`ekX+N>uI*_H=|uJGJ\ku	QZt	6ՔJt0*	D%4#L,E4LqJ ! 
+   ,         			
+
+
+!!!"""""""""######$$$$$$%%%&&&&&&''''''((()))***+++,,,---...000111222333333333444555666777888888999:::;;;<<<<<<===???@@@BBBCCCEEEFFFHHHHHHHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQQQQRRRSSSSSSTTTTTTUUUUUUVVVVVVWWWWWWXXXYYY[[[\\\___aaacccdddfffhhhiiikkkmmmoooqqqssstttuuuwwwxxxzzz{{{|||}}}~~~ 	8PM]#Yb5#0I	3	پJ3죅%usc}8R2S{fV%c>d\'LѝNmZ%K/ZH0@(L&PD`Zr0x[2i8	#8'Qu3z.5X/YLI.x˙@tSĴ$:帱,yD)Dqjt PJDJS&Q"I(^ǌ52Y･S+cNEkUVDE)
+d'K'd0JS5mn/>MXU+-Q,	tJ&4uK2!# {VS@ ! 
+   ,         			   !!!"""###$$$%%%&&&&&&'''((()))***+++,,,...000333666777888888999999999:::::::::::::::;;;;;;;;;<<<===>>>???@@@AAACCCDDDEEEFFFHHHJJJLLLMMMOOOPPPRRRSSSUUUXXXYYYZZZ[[[\\\^^^^^^___`````````aaabbbcccccceeefffgggggghhhiiiiiijjjjjjkkklllnnnoooppprrrsssuuuwwwzzz||| 	8Tk#fWq$SG	ֲD
+*<"#QƁ8]cHE?:),K}bB٬XDVU԰+PefH#('P\	&0רJF=F"	2kM)>dV*Tkwe'<UdEOBOiZlx'k@(LJr&u).0fیyqYkYTrQɑEŚљ0cѲ*J+=Mda{Ok߬KES,q_Q	s,	J%35#zvR@ ! 
+   ,         			
+
+
+   !!!"""###%%%&&&'''((()))***+++,,,---...///000111222333444555666888999:::;;;<<<======>>>>>>??????@@@@@@AAABBBCCCDDDFFFHHHJJJLLLMMMNNNOOOOOOPPPQQQRRRRRRSSSTTTTTTUUUWWWYYY[[[]]]^^^___```aaabbbcccdddeeegggiiilllooorrrvvvxxx{{{ 	8pעU{=#0.h]$U!T	ښ$
+"+A*@d('8$OZ#'վhL)mV@ERT+\5hJQ4Yd6, CI4c%Tbv>U7*c[@YM"ifeEVȬ`DJlY;8Q=HfR AI3gDT뚔ɕ3hD*ثHIca5	Rj!4ɕ0־Yda5TD7a@HQDc4
+T&e ! 
+   ,         			
+
+
+   !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666666777888::::::;;;<<<>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUUUUVVVWWWYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddfffmmmsssvvvyyy||| 	8Z\M4hmAR%
+b$$>$NZ碴^B%>gVGT}>A:M_!SM@'&c@d*qJk%]3}ɜoÐT2U-ܘÊG2<	g5~]Uf$)2<8#R3s!1&@Ir3
+	)還	3h@	
+<Idhؾhc	RjрN4._&[FUpB-") 0f4T& ! 
+   ,         			
+
+
+   !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIIIIKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkkkkkmmmnnnooopppuuuxxx 	8P$X5#Yea	
+%Ж&W	2i!,?	dUƄa2 IN
+<P],s520Y5s*1M~J:5'MˎJYk0⤋1aH&qEA4<O%W
+KըR1K@Z+óD1;|Jq)<E.ŦdI[lEph%?djǙIqJVWcI}eD2(ձf83GUngϯ}zD;!Gˤjɉ;Ae
+j;ء/1gU&t+"P!.uR3t	 ! 
+   ,         			
+
+
+   !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrr{{{ 	8PX5#Yec	%Ж&W	2ia,?	dhUƄa2 IN
+<P%^,q520Y5s*1M~H25'MˎJYk0⤋1aH&qEA4<O%W
+KըR1K@%*Y~$AJ<p(Q+1-[2zם::Z(й3y2N⊅q`)93־Z:+Nzꐲcq]F%s@$ࠊ<o	3gRʴ㠇YeF!B9I2P;$S@ ! 
+   ,         			
+
+
+   !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999::::::<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJJJJLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrzzz 	8PX5#Yec	%Ж&W	2ia,?	dhUƄa2 IN
+<P%^,q520Y5s*1M~H25'MˎJYk0⤋1aH&qEA4<O%W
+KըR1K@%*Y~$AJ<p(Q+/\2K$إF
+vgG%9"CYwJ-%)-}3))ΝVyHBȐ%ƞZvTz7֑v߳T;1a}A)~!$ͱ!JY'@<1_ ! 
+   ,         			   !!!"""###$$$%%%&&&''''''((()))+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKKKKMMMNNNOOOPPPQQQRRRRRRSSSTTTUUUWWWXXXYYYYYYZZZ[[[\\\^^^______aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnoooqqqrrrsss 	8PX5#Yec	
+%W	2ia,?i2*cBH0~J$a&CeBx,gZ&?x˓ePȃZyEV#yϢ C`ҫjTXL]IcSXu2pLY rX,
+/\1OإF
+0=>^^R§-<ZKZ`"aV6kJDM	[V'U@@H"+Q*Bu/0<'*$18Ck$"K_{1@zA@TL(P(u S@ ! 
+   ,      						
+
+
+
+
+
+   !!!###$$$%%%&&&''''''(((***+++---...///000000111222333333555666777999::::::;;;<<<===>>>>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMOOOPPPQQQRRRRRRSSSTTTUUUWWWXXXYYYYYYZZZ[[[\\\^^^___```aaaaaacccdddeeefffggghhhiiilllooosssxxx||| 	8$Z5#-fi
+%VX	Bi!A˄UƄa2UIN
+<
+Q^,u9BZ3LH2UXL̎Jyk*!h/ǆ"}z(PN*Z8pU<0TR2EW-S	Ԡ/Q@Z2iϢ%՝Q2]r*aPBf&0|"ȫ+f&!$l_S*'ZzVsEEn~o9@իT>c-\&8zrz4"&J	J!n1sKP2%@}%S@ ! 
+   ,      						
+
+
+
+
+
+   !!!"""######$$$%%%%%%%%%&&&'''(((***+++---///111222444444555666777888999:::;;;<<<<<<<<<======>>>???@@@AAABBBCCCDDDFFFFFFFFFGGGGGGHHHHHHHHHIIIJJJJJJLLLMMMOOOPPPQQQRRRSSSTTTUUUVVVXXXYYY[[[]]]^^^```aaacccdddfffgggjjjmmmppprrrtttuuuvvvxxx{{{}}} 	8֤Uy#P#\W%$PVT	Vj`׾U:iѩIc}MR 2:p٩Erl.?{QtmdLXHd3eAADPS%m2&p$Ī	QT
+BADJTMV)^ukћ57
+eYp|gN=iÇuժ)ѫ.9*Es1OxD	lӢDG<|	#xk)c&-䱔:p˴I׫QBJ!ʴ,}HJqZUiQ)r}MJTj_|0G52Ms&rX*	I"'KQ5CGrX@ ;
\ No newline at end of file
diff --git a/core/modules/edit/includes/form.inc b/core/modules/edit/includes/form.inc
new file mode 100644
index 0000000..573f162
--- /dev/null
+++ b/core/modules/edit/includes/form.inc
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @file
+ * Form callbacks for the Edit module.
+ */
+
+/**
+ * Field editing form. (For editing a field instance.)
+ *
+ * Proudly found elsewhere: FAPE module's fape_field_edit_form() and friends.
+ */
+function edit_field_form($form, &$form_state) {
+  $entity = $form_state['entity'];
+  $langcode = $form_state['langcode'];
+
+  // Attach the actual field form.
+  $options = array('field_name' =>  $form_state['field_name']);
+  field_attach_form($entity->entityType(), $entity, $form, $form_state, $langcode, $options);
+
+  $form['#validate'][] = 'edit_field_form_validate';
+  // @todo Verify that this is indeed not necessary anymore, see edit_field_form_validate().
+  // $form['#submit'][] = '';
+
+  // Add revisions form items if necessary.
+  // @todo We may be able to get rid of this when http://drupal.org/node/1678002 is solved.
+  list($use_revisions, $control_revisions) = edit_entity_allows_revisions($entity->entityType(), $entity->bundle(), $entity);
+  if ($use_revisions) {
+    $form_state['use revisions'] = TRUE;
+    $form['revision_information'] = array(
+      '#weight' => 11,
+    );
+
+    $form['revision_information']['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $entity->revision,
+      '#id' => 'edit-revision',
+      '#access' => $control_revisions,
+    );
+
+    if ($control_revisions || $entity->revision) {
+      $form['revision_information']['log'] = array(
+        '#type' => 'textarea',
+        '#title' => t('Log message'),
+        '#description' => t('Provide an explanation of the changes you are making. This will help other authors understand your motivations.'),
+        '#default_value' => $entity->log,
+      );
+
+      if ($control_revisions) {
+        $form['revision_information']['log']['#dependency'] = array('edit-revision' => array(1));
+      }
+    }
+    $form['#submit'][] = 'edit_field_form_revision_submit';
+  }
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  // Simplify the form.
+  _simplify_edit_field_edit_form($form);
+
+  return $form;
+}
+
+/**
+ * Helper function to simplify the field edit form for in-place editing.
+ */
+function _simplify_edit_field_edit_form(&$form) {
+  $elements = element_children($form);
+
+  // Required internal form properties.
+  $internal_elements = array('actions', 'form_build_id', 'form_token', 'form_id');
+
+  // Calculate the remaining form elements.
+  $remaining_elements = array_diff($elements, $internal_elements);
+
+  // Only simplify the form if there is a single element remaining.
+  if (count($remaining_elements) === 1) {
+    $element = $remaining_elements[0];
+
+    if ($form[$element]['#type'] == 'container') {
+      $language = $form[$element]['#language'];
+      $children = element_children($form[$element][$language]);
+
+      // Certain fields require different processing depending on the form
+      // structure.
+      if (count($children) == 0) {
+        // Checkbox elements don't have a title.
+        if ($form[$element][$language]['#type'] != 'checkbox') {
+          $form[$element][$language]['#title_display'] = 'invisible';
+        }
+      }
+      elseif (count($children) == 1) {
+        $form[$element][$language][0]['value']['#title_display'] = 'invisible';
+
+        // UX improvement: make the number of rows of textarea form elements
+        // fit the content. (i.e. no wads of whitespace)
+        if (isset($form[$element][$language][0]['value']['#type'])
+            && $form[$element][$language][0]['value']['#type'] == 'textarea')
+        {
+          $lines = count(explode("\n", $form[$element][$language][0]['value']['#default_value']));
+          $form[$element][$language][0]['value']['#rows'] = $lines + 1;
+        }
+      }
+    }
+
+    // Handle pseudo-fields that are language-independent, such as title,
+    // author, and creation date.
+    elseif (empty($form[$element]['#language'])) {
+      $form[$element]['#title_display'] = 'invisible';
+    }
+  }
+
+  // Make it easy for the JavaScript to identify the submit button.
+  $form['actions']['submit']['#attributes'] = array('class' => array('edit-form-submit'));
+}
+
+/**
+ * Validate field editing form.
+ */
+function edit_field_form_validate($form, &$form_state) {
+  $entity = $form_state['entity'];
+  $options = array('field_name' =>  $form_state['field_name']);
+
+  // 'submit' in D8 is for "building the entity object", not for actual
+  // submission. It appears though that if there were no validation errors, it
+  // is submitted automatically.
+  field_attach_submit($entity->entityType(), $entity, $form, $form_state, $options);
+
+  // Validation.
+  field_attach_form_validate($entity->entityType(), $entity, $form, $form_state, $options);
+}
+
+/**
+ * Submit callback that handles entity revisioning.
+ */
+function edit_field_form_revision_submit($form, &$form_state) {
+  $entity = $form_state['entity'];
+  if (!empty($form_state['use revisions'])) {
+    $entity->revision = $form_state['values']['revision'];
+    $entity->log = $form_state['values']['log'];
+  }
+}
diff --git a/core/modules/edit/includes/missing-api.inc b/core/modules/edit/includes/missing-api.inc
new file mode 100644
index 0000000..1b2f08a
--- /dev/null
+++ b/core/modules/edit/includes/missing-api.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @ingroup Missing in Entity API.
+ * @{
+ */
+
+// @todo Entity Access API: http://drupal.org/node/1696660#comment-6618944
+function edit_entity_access($op, $entity_type, $entity = NULL, $account = NULL) {
+  if ($entity_type == 'node') {
+    return node_access($op, $entity, $account);
+  }
+
+  // Currently, only editing of nodes is supported. See the above issue.
+  return FALSE;
+}
+
+// @todo Entity API check to see whether a specific entity supports revisions.
+// Proudly found elsewhere: FAPE module's _fape_entity_allows_revisions().
+function edit_entity_allows_revisions($entity_type, $bundle, $entity) {
+  $retval = array(FALSE, FALSE);
+
+  switch ($entity_type) {
+    case 'node':
+      $node_options = variable_get('node_options_' . $bundle, array('status', 'promote'));
+      $retval[0] = in_array('revision', $node_options);
+      $retval[1] = user_access('administer nodes');
+      break;
+
+    default:
+      $entity_info = entity_get_info($entity_type);
+      $retval[0] = !empty($entity_info['revision table']);
+      break;
+  }
+
+  $entity->revision = $retval[0];
+  $entity->log = '';
+  return $retval;
+}
+
+/**
+ * @} End of "ingroup Missing in Entity API.".
+ */
diff --git a/core/modules/edit/includes/pages.inc b/core/modules/edit/includes/pages.inc
new file mode 100644
index 0000000..d59442a
--- /dev/null
+++ b/core/modules/edit/includes/pages.inc
@@ -0,0 +1,179 @@
+<?php
+/**
+ * @file
+ * AJAX endpoints to retrieve & save subforms for fields and re-render fields.
+ */
+
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Page callback: Provides editing of entity fields.
+ *
+ * @param string $entity_type
+ *   The entity type of the entity that is being edited.
+ * @param int $entity_id
+ *   The entity ID of the entity that is being edited.
+ * @param string $field_name
+ *   The name of the field that is being edited.
+ * @param string $langcode
+ *   The name of the language for which the field is being edited.
+ * @param string $view_mode
+ *   The view mode the field should be rerendered in.
+ * @return array
+ *   A render array.
+ */
+function edit_field_edit($entity_type, $entity_id, $field_name, $langcode, $view_mode) {
+  // Ensure the entity type is valid.
+  if (empty($entity_type)) {
+    throw new NotFoundHttpException();
+  }
+
+  $entity_info = entity_get_info($entity_type);
+  if (!$entity_info) {
+    throw new NotFoundHttpException();
+  }
+
+  $entities = entity_load_multiple($entity_type, array($entity_id));
+  if (!$entities) {
+    throw new NotFoundHttpException();
+  }
+
+  $entity = reset($entities);
+  if (!$entity) {
+    throw new NotFoundHttpException();
+  }
+
+  // Ensure a valid language code is set.
+  $langcode = field_valid_language($langcode);
+
+  // Ensure access to update this particular entity is granted.
+  if (!edit_entity_access('update', $entity_type, $entity)) {
+    throw new AccessDeniedHttpException();
+  }
+
+  // Ensure access to update this particular field is granted.
+  if (!field_access('edit', $field_name, $entity_type, $entity)) {
+    throw new AccessDeniedHttpException();
+  }
+
+  $field_instance = field_info_instance($entity_type, $field_name, $entity->bundle());
+  if (empty($field_instance)) {
+    throw new NotFoundHttpException();
+  }
+
+  $form_state = array(
+    'entity' => $entity,
+    'field_name' => $field_name,
+    'langcode' => $langcode,
+    'no_redirect' => TRUE,
+    'build_info' => array('args' => array()),
+  );
+  $commands = array();
+  form_load_include($form_state, 'inc', 'edit', 'includes/form');
+  $form = drupal_build_form('edit_field_form', $form_state);
+
+  $id = "$entity_type:$entity_id:$field_name:$langcode:$view_mode";
+  if (!empty($form_state['executed'])) {
+    $form_state['entity']->save();
+
+    $options = array('field_name' => $field_name);
+    field_attach_prepare_view($entity->entityType(), array($entity->id() => $entity), $view_mode, $langcode, $options);
+    $output = field_attach_view($entity->entityType(), $entity, $view_mode, $langcode, $options);
+
+    $commands[] = array(
+      'command' => 'edit_field_form_saved',
+      'id'      => $id,
+      'data'    => drupal_render($output),
+    );
+  }
+  else {
+    $commands[] = array(
+      'command' => 'edit_field_form',
+      'id'      => $id,
+      'data'    => drupal_render($form),
+    );
+  }
+
+  // When working with a hidden form, we don't want any CSS or JS to be loaded.
+  if (isset($_POST['nocssjs']) && $_POST['nocssjs'] === 'true') {
+    drupal_static_reset('drupal_add_css');
+    drupal_static_reset('drupal_add_js');
+  }
+
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+/**
+ * Page callback: render a processed text field without transformation filters.
+ *
+ * @param string $entity_type
+ *   The entity type of the entity of which a processed text field is being
+ *   rerendered.
+ * @param int $entity_id
+ *   The entity ID of the entity of which a processed text field is being
+ *   rerendered.
+ * @param string $field_name
+ *   The name of the (processed text) field that that is being rerendered
+ * @param string $langcode
+ *   The name of the language for which the processed text field is being
+ *   rererendered.
+ * @param string $view_mode
+ *   The view mode the processed text field should be rerendered in.
+ * @return array
+ *   A render array.
+ */
+function edit_text_field_render_without_transformation_filters($entity_type, $entity_id, $field_name, $langcode, $view_mode) {
+  // Ensure the entity type is valid.
+  if (empty($entity_type)) {
+    throw new NotFoundHttpException();
+  }
+
+  $entity_info = entity_get_info($entity_type);
+  if (!$entity_info) {
+    throw new NotFoundHttpException();
+  }
+
+  $entities = entity_load_multiple($entity_type, array($entity_id));
+  if (!$entities) {
+    throw new NotFoundHttpException();
+  }
+
+  $entity = reset($entities);
+  if (!$entity) {
+    throw new NotFoundHttpException();
+  }
+
+  // Ensure a valid language code is set.
+  $langcode = field_valid_language($langcode);
+
+  // Ensure access to update this particular entity is granted.
+  if (!edit_entity_access('update', $entity_type, $entity)) {
+    throw new AccessDeniedHttpException();
+  }
+
+  // Ensure access to update this particular field is granted.
+  if (!field_access('edit', $field_name, $entity_type, $entity)) {
+    throw new AccessDeniedHttpException();
+  }
+
+  $field_instance = field_info_instance($entity_type, $field_name, $entity->bundle());
+  if (empty($field_instance)) {
+    throw new NotFoundHttpException();
+  }
+
+  $commands = array();
+
+  // Render the field in our custom display mode; retrieve the re-rendered
+  // markup, this is what we're after.
+  $field_output = field_view_field($entity_type, $entity, $field_name, 'edit-render-without-transformation-filters');
+  $output = $field_output[0]['#markup'];
+
+  $commands[] = array(
+    'command' => 'edit_field_rendered_without_transformation_filters',
+    'id'      => "$entity_type:$entity_id:$field_name:$langcode:$view_mode",
+    'data'    => $output,
+  );
+
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
diff --git a/core/modules/edit/js/ajax.js b/core/modules/edit/js/ajax.js
new file mode 100644
index 0000000..f2bdd3b
--- /dev/null
+++ b/core/modules/edit/js/ajax.js
@@ -0,0 +1,124 @@
+(function($) {
+
+/**
+ * @file ajax.js
+ *
+ * AJAX commands for Edit module.
+ */
+
+// Hide these in a ready to ensure that Drupal.ajax is set up first.
+$(function() {
+  Drupal.ajax.prototype.commands.edit_field_form = function(ajax, response, status) {
+    console.log('edit_field_form', ajax, response, status);
+
+    // Only apply the form immediately if this form is currently being edited.
+    if (Drupal.edit.state.editedEditable == response.id && ajax.$field.hasClass('edit-type-form')) {
+      Drupal.ajax.prototype.commands.insert(ajax, {
+        data: response.data,
+        selector: '.edit-form-container .placeholder'
+      });
+
+      // Indicate in the 'info' toolgroup that the form has loaded, but only do
+      // it after half a second to prevent it from flashing, which is bad UX.
+      setTimeout(function() {
+        Drupal.edit.toolbar.removeClass(ajax.$editable, 'info', 'loading');
+      }, 500);
+
+      // Detect changes in this form.
+      Drupal.edit.form.get(ajax.$editable)
+      .delegate(':input', 'formUpdated.edit', function() {
+        ajax.$editable
+        .data('edit-content-changed', true)
+        .trigger('edit-content-changed.edit');
+      })
+      .delegate('input', 'keypress.edit', function(event) {
+        if (event.keyCode == 13) {
+          return false;
+        }
+      });
+
+      var $submit = Drupal.edit.form.get(ajax.$editable).find('.edit-form-submit');
+      var element_settings = {
+        url : $submit.closest('form').attr('action'),
+        setClick : true,
+        event : 'click.edit',
+        progress : { type : 'throbber' },
+        // IPE-specific settings.
+        $editable : ajax.$editable,
+        $field : ajax.$field
+      };
+      var base = $submit.attr('id');
+      Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
+
+      // Give focus to the first input in the form.
+      //$('.edit-form').find('form :input:visible:enabled:first').focus()
+    }
+    else if (Drupal.edit.state.editedEditable == response.id && ajax.$field.hasClass('edit-type-direct')) {
+      Drupal.edit.state.directEditableFormResponse = response;
+      $('#edit_backstage').append(response.data);
+
+      var $submit = $('#edit_backstage form .edit-form-submit');
+      var element_settings = {
+        url : $submit.closest('form').attr('action'),
+        setClick : true,
+        event : 'click.edit',
+        progress : { type : 'throbber' },
+        // IPE-specific settings.
+        $editable : ajax.$editable,
+        $field : ajax.$field
+      };
+      var base = $submit.attr('id');
+      Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings);
+    }
+    else {
+      console.log('queueing', response);
+    }
+
+    // Animations.
+    Drupal.edit.toolbar.show(ajax.$editable, 'ops');
+    ajax.$editable.trigger('edit-form-loaded.edit');
+  };
+  Drupal.ajax.prototype.commands.edit_field_rendered_without_transformation_filters = function(ajax, response, status) {
+    console.log('edit_field_rendered_without_transformation_filters', ajax, response, status);
+
+    if (Drupal.edit.state.editedEditable == response.id
+        && ajax.$field.hasClass('edit-type-direct')
+        && ajax.$field.hasClass('edit-text-with-transformation-filters')
+        )
+    {
+      // Indicate in the 'info' toolgroup that the form has loaded, but only do
+      // it after half a second to prevent it from flashing, which is bad UX.
+      setTimeout(function() {
+        Drupal.edit.toolbar.removeClass(ajax.$editable, 'info', 'loading');
+      }, 500);
+
+      // Update the HTML of the editable and enable WYSIWYG editing on it.
+      ajax.$editable.html(response.data);
+      Drupal.edit.editables._wysiwygify(ajax.$editable);
+    }
+  };
+  Drupal.ajax.prototype.commands.edit_field_form_saved = function(ajax, response, status) {
+    console.log('edit_field_form_saved', ajax, response, status);
+
+    // Stop the editing.
+    Drupal.edit.editables.stopEdit(ajax.$editable);
+
+    // Response.data contains the updated rendering of the field, if any.
+    if (response.data) {
+      // Replace the old content with the new content.
+      var $field = $('.edit-field[data-edit-id="' + response.id  + '"]');
+      var $parent = $field.parent();
+      if ($field.css('display') == 'inline') {
+        $parent.html(response.data);
+      }
+      else {
+        $field.replaceWith(response.data);
+      }
+
+      // Make the freshly rendered field(s) in-place-editable again.
+      Drupal.edit.startEditableFields(Drupal.edit.findEditableFields($parent));
+    }
+  };
+});
+
+})(jQuery);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
new file mode 100644
index 0000000..d851ee2
--- /dev/null
+++ b/core/modules/edit/js/edit.js
@@ -0,0 +1,689 @@
+(function ($) {
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.wysiwyg = Drupal.edit.wysiwyg || {};
+
+/**
+ * Attach toggling behavior and in-place editing.
+ */
+Drupal.behaviors.edit = {
+  attach: function(context) {
+    $('#edit_view-edit-toggles').once('edit-init', Drupal.edit.init);
+    $('#edit_view-edit-toggles').once('edit-toggle', Drupal.edit.toggle.render);
+
+    // TODO: remove this; this is to make the current prototype somewhat usable.
+    $('a.edit_view-edit-toggle').click(function() {
+      $(this).trigger('click.edit');
+    });
+  }
+};
+
+Drupal.edit.const = {};
+Drupal.edit.const.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit";
+
+Drupal.edit.init = function() {
+  Drupal.edit.state = {};
+  // We always begin in view mode.
+  Drupal.edit.state.isViewing = true;
+  Drupal.edit.state.fieldBeingHighlighted = [];
+  Drupal.edit.state.fieldBeingEdited = [];
+  Drupal.edit.state.higlightedEditable = null;
+  Drupal.edit.state.editedEditable = null;
+  Drupal.edit.state.queues = {};
+  Drupal.edit.state.wysiwygReady = false;
+
+  // Build inventory.
+  var IDMapper = function() { return Drupal.edit.getID($(this)); };
+  Drupal.edit.state.fields = Drupal.edit.findEditableFields().map(IDMapper);
+  console.log('Fields:', Drupal.edit.state.fields.length, ';', Drupal.edit.state.fields);
+
+  // Form preloader.
+  Drupal.edit.state.queues.preload = Drupal.edit.findEditableFields().filter('.edit-type-form').map(IDMapper);
+  console.log('Fields with (server-generated) forms:', Drupal.edit.state.queues.preload);
+
+  // Initialize WYSIWYG, if any.
+  if (Drupal.settings.edit.wysiwyg) {
+    $(document).bind('edit-wysiwyg-ready.edit', function() {
+      Drupal.edit.state.wysiwygReady = true;
+      console.log('edit: WYSIWYG ready');
+    });
+    Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].init();
+  }
+
+  // Create a backstage area.
+  $(Drupal.theme('editBackstage', {})).appendTo('body');
+
+  // Transition between view/edit states.
+  $("a.edit_view-edit-toggle").bind('click.edit', function() {
+    var wasViewing = Drupal.edit.state.isViewing;
+    var isViewing  = Drupal.edit.state.isViewing = $(this).hasClass('edit-view');
+    // Swap active class among the two links.
+    $('a.edit_view-edit-toggle').removeClass('active');
+    $('a.edit_view-edit-toggle').parent().removeClass('active');
+    $('a.edit_view-edit-toggle.edit-' + (isViewing ? 'view' : 'edit')).addClass('active');
+    $('a.edit_view-edit-toggle.edit-' + (isViewing ? 'view' : 'edit')).parent().addClass('active');
+
+    if (wasViewing && !isViewing) {
+      $(Drupal.theme('editOverlay', {}))
+      .appendTo('body')
+      .addClass('edit-animate-slow edit-animate-invisible')
+      .bind('click.edit', Drupal.edit.clickOverlay);;
+
+      var $f = Drupal.edit.findEditableFields();
+      Drupal.edit.startEditableFields($f);
+
+      // TODO: preload forms. We could do one request per form, but that's more
+      // RTTs than needed. Instead, the server should support batch requests.
+      console.log('Preloading forms that we might need!', Drupal.edit.state.queues.preload);
+
+      // Animations. Integrate with both navbar and toolbar.
+      $('#edit_overlay').css('top', $('#navbar, #toolbar').outerHeight());
+      $('#edit_overlay').removeClass('edit-animate-invisible');
+
+      // Disable contextual links in edit mode.
+      $('.contextual-links-region')
+      .addClass('edit-contextual-links-region')
+      .removeClass('contextual-links-region');
+    }
+    else if (!wasViewing && isViewing) {
+      // Animations.
+      $('#edit_overlay')
+      .addClass('edit-animate-invisible')
+      .bind(Drupal.edit.const.transitionEnd, function(e) {
+        $('#edit_overlay, .edit-form-container, .edit-toolbar-container, #edit_modal, #edit_backstage').remove();
+      });
+
+      var $f = Drupal.edit.findEditableFields();
+      Drupal.edit.stopEditableFields($f);
+
+      // Re-enable contextual links in view mode.
+      $('.edit-contextual-links-region')
+      .addClass('contextual-links-region')
+      .removeClass('edit-contextual-links-region');
+    }
+    else {
+      // No state change.
+    }
+    return false;
+  });
+};
+
+Drupal.edit.findEditableFields = function(context) {
+  return $('.edit-field.edit-allowed', context || Drupal.settings.edit.context);
+};
+
+/*
+ * findEditableFields() just looks for fields that are editable, i.e. for the
+ * field *wrappers*. Depending on the field, however, either the whole field wrapper
+ * will be marked as editable (in this case, an inline form will be used for editing),
+ * *or* a specific (field-specific even!) DOM element within that field wrapper will be
+ * marked as editable.
+ * This function is for finding the *editables* themselves, given the *editable fields*.
+ */
+Drupal.edit.findEditablesForFields = function($fields) {
+  var $editables = $();
+
+  // type = form
+  $editables = $editables.add($fields.filter('.edit-type-form'));
+
+  // type = direct
+  var $direct = $fields.filter('.edit-type-direct');
+  $editables = $editables.add($direct.find('.field-item'));
+  // Edge case: "title" pseudofield on pages with lists of nodes.
+  $editables = $editables.add($direct.filter('h2').find('a'));
+  // Edge case: "title" pseudofield on node pages.
+  $editables = $editables.add($direct.find('h1'));
+
+  return $editables;
+};
+
+Drupal.edit.getID = function($field) {
+  return $field.data('edit-id');
+};
+
+Drupal.edit.findFieldForID = function(id, context) {
+  return $('[data-edit-id="' + id + '"]', context || $('#content'));
+};
+
+Drupal.edit.findFieldForEditable = function($editable) {
+  return $editable.filter('.edit-type-form').length ? $editable : $editable.closest('.edit-type-direct');
+};
+
+Drupal.edit.startEditableFields = function($fields) {
+  var $fields = $fields.once('edit');
+  // Ignore fields that need a WYSIWYG editor if no WYSIWYG editor is present
+  if (!Drupal.settings.edit.wysiwyg) {
+    $fields = $fields.filter(':not(.edit-type-direct-with-wysiwyg)');
+  }
+  var $editables = Drupal.edit.findEditablesForFields($fields);
+
+  $editables
+  .addClass('edit-animate-fast')
+  .addClass('edit-candidate edit-editable')
+  .bind('mouseenter.edit', function(e) {
+    var $editable = $(this);
+    Drupal.edit.util.ignoreHoveringVia(e, '.edit-toolbar-container', function() {
+      console.log('field:mouseenter');
+      if (!$editable.hasClass('edit-editing')) {
+        Drupal.edit.editables.startHighlight($editable);
+      }
+    });
+  })
+  .bind('mouseleave.edit', function(e) {
+    var $editable = $(this);
+    Drupal.edit.util.ignoreHoveringVia(e, '.edit-toolbar-container', function() {
+      console.log('field:mouseleave');
+      if (!$editable.hasClass('edit-editing')) {
+        Drupal.edit.editables.stopHighlight($editable);
+      }
+    });
+  })
+  .bind('click.edit', function() {
+    Drupal.edit.editables.startEdit($(this)); return false;
+  })
+  // Some transformations are editable-specific.
+  .map(function() {
+    $(this).data('edit-background-color', Drupal.edit.util.getBgColor($(this)));
+  });
+};
+
+Drupal.edit.stopEditableFields = function($fields) {
+  var $editables = Drupal.edit.findEditablesForFields($fields);
+
+  $fields
+  .removeClass('edit-processed');
+
+  $editables
+  .removeClass('edit-candidate edit-editable edit-highlighted edit-editing edit-belowoverlay')
+  .unbind('mouseenter.edit mouseleave.edit click.edit edit-content-changed.edit')
+  .removeAttr('contenteditable')
+  .removeData(['edit-content-original', 'edit-content-changed']);
+};
+
+Drupal.edit.clickOverlay = function(e) {
+  console.log('clicked overlay');
+
+  if (Drupal.edit.modal.get().length == 0) {
+    Drupal.edit.toolbar.get(Drupal.edit.state.fieldBeingEdited)
+    .find('a.close').trigger('click.edit');
+  }
+};
+
+/*
+2. Each field MAY (if it is editable by the user) contain exactly one Editable,
+   in which the editing itself occurs, this can be either:
+     a. type=direct, here some child element of the Field element is marked as editable
+     b. type=form, here the field itself is marked as editable, upon edit, a form is used
+ */
+
+// Field editables.
+Drupal.edit.editables = {
+  startHighlight: function($editable) {
+    console.log('editables.startHighlight');
+    if (Drupal.edit.toolbar.create($editable)) {
+      var label = $editable.filter('.edit-type-form').data('edit-field-label')
+        || $editable.closest('.edit-type-direct').data('edit-field-label');
+
+      Drupal.edit.toolbar.get($editable)
+      .find('.edit-toolbar:not(:has(.edit-toolgroup.info))')
+      .append(Drupal.theme('editToolgroup', {
+        classes: 'info',
+        buttons: [
+          { url: '#', label: label, classes: 'blank-button label', hasButtonRole: false },
+        ]
+      }))
+      .delegate('a.label', 'click.edit', function(e) {
+        // Clicking the label equals clicking the editable itself.
+        $editable.trigger('click.edit');
+        return false;
+      });
+    }
+
+    // Animations.
+    setTimeout(function() {
+      $editable.addClass('edit-highlighted');
+      Drupal.edit.toolbar.show($editable, 'info');
+    }, 0);
+
+    Drupal.edit.state.fieldBeingHighlighted = $editable;
+    Drupal.edit.state.higlightedEditable = Drupal.edit.getID(Drupal.edit.findFieldForEditable($editable));
+  },
+
+  stopHighlight: function($editable) {
+    console.log('editables.stopHighlight');
+    if ($editable.length == 0) {
+      return;
+    }
+
+    // Animations.
+    Drupal.edit.toolbar.remove($editable);
+    $editable.removeClass('edit-highlighted');
+
+    Drupal.edit.state.fieldBeingHighlighted = [];
+    Drupal.edit.state.highlightedEditable = null;
+  },
+
+  startEdit: function($editable) {
+    if ($editable.hasClass('edit-editing')) {
+      return;
+    }
+
+    console.log('editables.startEdit: ', $editable);
+    var self = this;
+    var $field = Drupal.edit.findFieldForEditable($editable);
+
+    // Highlight if not already highlighted.
+    if (Drupal.edit.state.fieldBeingHighlighted[0] != $editable[0]) {
+      Drupal.edit.editables.startHighlight($editable);
+    }
+
+    $editable
+    .addClass('edit-editing')
+    .bind('edit-content-changed.edit', function(e) {
+      self._buttonFieldSaveToBlue(e, $editable, $field);
+    })
+    // Some transformations are editable-specific.
+    .map(function() {
+      $(this).css('background-color', $(this).data('edit-background-color'));
+    });
+
+    // While editing, don't show *any* other field as editable.
+    $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
+
+    // Toolbar (already created in the highlight).
+    Drupal.edit.toolbar.get($editable)
+    .addClass('edit-editing')
+    .find('.edit-toolbar:not(:has(.edit-toolgroup.ops))')
+    .append(Drupal.theme('editToolgroup', {
+      classes: 'ops',
+      buttons: [
+        { url: '#', label: Drupal.t('Save'), classes: 'field-save save gray-button' },
+        { url: '#', title: 'Undo changes', label: '<span class="close"></span>', classes: 'field-close close gray-button' }
+      ]
+    }))
+    .delegate('a.field-save', 'click.edit', function(e) {
+      return self._buttonFieldSaveClicked(e, $editable, $field);
+    })
+    .delegate('a.field-close', 'click.edit', function(e) {
+      return self._buttonFieldCloseClicked(e, $editable, $field);
+    });
+
+    // Changes to $editable based on the type.
+    var callback = ($field.hasClass('edit-type-direct'))
+      ? self._updateDirectEditable
+      : self._updateFormEditable;
+    callback($editable);
+
+    // Regardless of the type, load the form for this field. We always use forms
+    // to submit the changes.
+    self._loadForm($editable, $field);
+
+    Drupal.edit.state.fieldBeingEdited = $editable;
+    Drupal.edit.state.editedEditable = Drupal.edit.getID($field);
+  },
+
+  stopEdit: function($editable) {
+    console.log('editables.stopEdit: ', $editable);
+    var self = this;
+    var $field = Drupal.edit.findFieldForEditable($editable);
+    if ($editable.length == 0) {
+      return;
+    }
+
+    $editable
+    .removeClass('edit-highlighted edit-editing edit-belowoverlay')
+    // Some transformations are editable-specific.
+    .map(function() {
+      $(this).css('background-color', '');
+    });
+
+    // Make the other fields and entities editable again.
+    $('.edit-candidate').addClass('edit-editable');
+
+    // Changes to $editable based on the type.
+    var callback = ($field.hasClass('edit-type-direct'))
+      ? self._restoreDirectEditable
+      : self._restoreFormEditable;
+    callback($editable);
+
+    Drupal.edit.toolbar.remove($editable);
+    Drupal.edit.form.remove($editable);
+
+    Drupal.edit.state.fieldBeingEdited = [];
+    Drupal.edit.state.editedEditable = null;
+  },
+
+  _loadRerenderedProcessedText: function($editable, $field) {
+    // Indicate in the 'info' toolgroup that the form is loading.
+    Drupal.edit.toolbar.addClass($editable, 'info', 'loading');
+
+    var edit_id = Drupal.edit.getID($field);
+    var element_settings = {
+      url      : Drupal.edit.util.calcRerenderProcessedTextURL(edit_id),
+      event    : 'edit-internal-load-rerender.edit',
+      $field   : $field,
+      $editable: $editable,
+      submit   : { nocssjs : true },
+      progress : { type : null }, // No progress indicator.
+    };
+    if (Drupal.ajax.hasOwnProperty(edit_id)) {
+      delete Drupal.ajax[edit_id];
+      $editable.unbind('edit-internal-load-rerender.edit');
+    }
+    Drupal.ajax[edit_id] = new Drupal.ajax(edit_id, $editable, element_settings);
+    $editable.trigger('edit-internal-load-rerender.edit');
+  },
+
+  // Attach, activate and show the WYSIWYG editor.
+  _wysiwygify: function($editable) {
+    var $field = Drupal.edit.findFieldForEditable($editable);
+    $editable.addClass('edit-wysiwyg-attached');
+    var formatID = $field.attr('data-edit-text-format');
+    var format = Drupal.settings.aloha.formats[formatID];
+    Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].attach($editable, format);
+    Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].activate($editable);
+    Drupal.edit.toolbar.show($editable, 'wysiwyg-tabs');
+    Drupal.edit.toolbar.show($editable, 'wysiwyg');
+  },
+
+  _updateDirectEditable: function($editable) {
+    var $field = Drupal.edit.findFieldForEditable($editable);
+
+    Drupal.edit.editables._padEditable($editable, $field);
+
+    if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
+      Drupal.edit.toolbar.get($editable)
+      .find('.edit-toolbar:not(:has(.edit-toolgroup.wysiwyg-tabs))')
+      .append(Drupal.theme('editToolgroup', {
+        classes: 'wysiwyg-tabs',
+        buttons: []
+      }))
+      .end()
+      .find('.edit-toolbar:not(:has(.edit-toolgroup.wysiwyg))')
+      .append(Drupal.theme('editToolgroup', {
+        classes: 'wysiwyg',
+        buttons: []
+      }));
+
+      // 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 ($field.hasClass('edit-text-with-transformation-filters')) {
+        Drupal.edit.editables._loadRerenderedProcessedText($editable, $field);
+        // Also store the "real" original content, i.e. the transformed one.
+        $editable.data('edit-content-original-transformed', $editable.html())
+      }
+      // When no transformation filters have been applied: start WYSIWYG editing
+      // immediately!
+      else {
+        setTimeout(function() {
+          Drupal.edit.editables._wysiwygify($editable);
+        }, 0);
+      }
+    }
+    else {
+      $editable.attr('contenteditable', true);
+    }
+
+    $editable
+    .data('edit-content-original', $editable.html())
+    .data('edit-content-changed', false);
+
+    // Detect content changes ourselves only when not using a WYSIWYG editor.
+    var markContentChanged = function() {
+      $editable.data('edit-content-changed', true);
+      $editable.trigger('edit-content-changed.edit');
+    };
+    if (!$field.hasClass('edit-type-direct-with-wysiwyg')) {
+      // We cannot use Drupal.behaviors.formUpdated here because we're not dealing
+      // with a form!
+      $editable
+      .bind('blur.edit keyup.edit paste.edit', function() {
+        if ($editable.html() != $editable.data('edit-content-original')) {
+          markContentChanged();
+        }
+      })
+      // Disallow return/enter key when editing titles.
+      .bind('keypress.edit', function(event) {
+        if (event.keyCode == 13) {
+          return false;
+        }
+      });
+    }
+    else {
+      $editable.bind('edit-wysiwyg-content-changed.edit', function() {
+        markContentChanged();
+      });
+    }
+  },
+
+  _restoreDirectEditable: function($editable) {
+    var $field = Drupal.edit.findFieldForEditable($editable);
+
+    Drupal.edit.editables._padEditable($editable, $field);
+
+    if ($field.hasClass('edit-type-direct-with-wysiwyg') && $editable.hasClass('edit-wysiwyg-attached')) {
+      $editable.removeClass('edit-wysiwyg-attached');
+      Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].detach($editable);
+
+      // Work-around for major AE bug. See:
+      //  - http://drupal.org/node/1725032
+      //  - https://github.com/alohaeditor/Aloha-Editor/issues/693.
+      // Also unbind to make sure this doesn't break anything when using
+      // this version of edit.js with a fixed version of Aloha Editor.
+      $editable
+      .unbind('click.edit')
+      .bind('click.edit', function() {
+        Drupal.edit.editables.startEdit($(this)); return false;
+      });
+    }
+    else {
+      $editable
+      .removeAttr('contenteditable')
+      .unbind('keypress.edit');
+    }
+
+    Drupal.edit.editables._unpadEditable($editable, $field);
+
+    $editable
+    .removeData(['edit-content-original', 'edit-content-changed', 'edit-content-original-transformed'])
+    .unbind('blur.edit keyup.edit paste.edit edit-content-changed.edit');
+
+    // Not only clean up the changes to $editable, but also clean up the
+    // backstage area, where we hid the form that we used to send the changes.
+    $('#edit_backstage form').remove();
+  },
+
+  _padEditable: function($editable, $field) {
+    // Add 5px padding for readability. This means we'll freeze the current
+    // width and *then* add 5px padding, hence ensuring the padding is added "on
+    // the outside".
+    // 1) Freeze the width (if it's not already set); don't use animations.
+    if ($editable[0].style.width === "") {
+      $editable
+      .data('edit-width-empty', true)
+      .addClass('edit-animate-disable-width')
+      .css('width', $editable.width());
+    }
+    // 2) Add padding; use animations.
+    var posProp = Drupal.edit.util.getPositionProperties($editable);
+    var $toolbar = Drupal.edit.toolbar.get($editable);
+    setTimeout(function() {
+      // Re-enable width animations (padding changes affect width too!).
+      $editable.removeClass('edit-animate-disable-width');
+
+      // The toolbar must move to the top and the left.
+      var $hf = $toolbar.find('.edit-toolbar-heightfaker');
+      $hf.css({ bottom: '6px', left: '-5px' });
+      // When using a WYSIWYG editor, the width of the toolbar must match the
+      // width of the editable.
+      if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
+        $hf.css({ width: $editable.width() + 10 });
+      }
+
+      // Pad the editable.
+      $editable
+      .css({
+        'position': 'relative',
+        'top':  posProp['top']  - 5 + 'px',
+        'left': posProp['left'] - 5 + 'px',
+        'padding-top'   : posProp['padding-top']    + 5 + 'px',
+        'padding-left'  : posProp['padding-left']   + 5 + 'px',
+        'padding-right' : posProp['padding-right']  + 5 + 'px',
+        'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+        'margin-bottom':  posProp['margin-bottom'] - 10 + 'px',
+      });
+    }, 0);
+  },
+
+  _unpadEditable: function($editable, $field) {
+    // 1) Set the empty width again.
+    if ($editable.data('edit-width-empty') === true) {
+      console.log('restoring width');
+      $editable
+      .addClass('edit-animate-disable-width')
+      .css('width', '');
+    }
+    // 2) Remove padding; use animations (these will run simultaneously with)
+    // the fading out of the toolbar as its gets removed).
+    var posProp = Drupal.edit.util.getPositionProperties($editable);
+    var $toolbar = Drupal.edit.toolbar.get($editable);
+    setTimeout(function() {
+      // Re-enable width animations (padding changes affect width too!).
+      $editable.removeClass('edit-animate-disable-width');
+
+      // Move the toolbar back to its original position.
+      var $hf = $toolbar.find('.edit-toolbar-heightfaker');
+      $hf.css({ bottom: '1px', left: '' });
+      // When using a WYSIWYG editor, restore the width of the toolbar.
+      if ($field.hasClass('edit-type-direct-with-wysiwyg')) {
+        $hf.css({ width: '' });
+      }
+
+      // Unpad the editable.
+      $editable
+      .css({
+        'position': 'relative',
+        'top':  posProp['top']  + 5 + 'px',
+        'left': posProp['left'] + 5 + 'px',
+        'padding-top'   : posProp['padding-top']    - 5 + 'px',
+        'padding-left'  : posProp['padding-left']   - 5 + 'px',
+        'padding-right' : posProp['padding-right']  - 5 + 'px',
+        'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+        'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+      });
+    }, 0);
+  },
+
+  // Creates a form container; when the $editable is inline, it will inherit CSS
+  // properties from the toolbar container, so the toolbar must already exist.
+  _updateFormEditable: function($editable) {
+    if (Drupal.edit.form.create($editable)) {
+      $editable
+      .addClass('edit-belowoverlay')
+      .removeClass('edit-highlighted edit-editable');
+
+      Drupal.edit.form.get($editable)
+      .find('.edit-form')
+      .addClass('edit-editable edit-highlighted edit-editing')
+      .css('background-color', $editable.data('edit-background-color'));
+    }
+  },
+
+  _restoreFormEditable: function($editable) {
+    // No need to do anything here; all of the field HTML will be overwritten
+    // with the freshly rendered version from the server anyway!
+  },
+
+  _loadForm: function($editable, $field) {
+    var edit_id = Drupal.edit.getID($field);
+    var element_settings = {
+      url      : Drupal.edit.util.calcFormURLForField(edit_id),
+      event    : 'edit-internal.edit',
+      $field   : $field,
+      $editable: $editable,
+      submit   : { nocssjs : ($field.hasClass('edit-type-direct')) },
+      progress : { type : null }, // No progress indicator.
+    };
+    if (Drupal.ajax.hasOwnProperty(edit_id)) {
+      delete Drupal.ajax[edit_id];
+      $editable.unbind('edit-internal.edit');
+    }
+    Drupal.ajax[edit_id] = new Drupal.ajax(edit_id, $editable, element_settings);
+    $editable.trigger('edit-internal.edit');
+  },
+
+  _buttonFieldSaveToBlue: function(e, $editable, $field) {
+    Drupal.edit.toolbar.get($editable)
+    .find('a.save').addClass('blue-button').removeClass('gray-button');
+  },
+
+  _buttonFieldSaveClicked: function(e, $editable, $field) {
+    // type = form
+    if ($field.hasClass('edit-type-form')) {
+      Drupal.edit.form.get($field).find('form')
+      .find('.edit-form-submit').trigger('click.edit').end();
+    }
+    // type = direct
+    else if ($field.hasClass('edit-type-direct')) {
+      $editable.blur();
+
+      var wysiwyg = $field.hasClass('edit-type-direct-with-wysiwyg')
+          && $editable.hasClass('edit-wysiwyg-attached');
+
+      // When using WYSIWYG editing, first detach the WYSIWYG editor to ensure
+      // the content has been cleaned up before saving it. (Otherwise,
+      // annotations and infrastructure created by the WYSIWYG editor could also
+      // get saved).
+      if (wysiwyg) {
+        $editable.removeClass('edit-wysiwyg-attached');
+        Drupal.edit.wysiwyg[Drupal.settings.edit.wysiwyg].detach($editable);
+      }
+
+      // We trim the title because otherwise whitespace in the raw HTML ends
+      // up in the title as well.
+      // TRICKY: Drupal core does not trim the title, so in theory this is
+      // out of line with Drupal core's behavior.
+      var value = (wysiwyg)
+        ? $.trim($editable.html())
+        : $.trim($editable.text());
+      var selector = (wysiwyg)
+        ? 'textarea'
+        : ':input[type!="hidden"][type!="submit"]';
+      $('#edit_backstage form')
+      .find(selector).val(value).end()
+      .find('.edit-form-submit').trigger('click.edit');
+    }
+    return false;
+  },
+
+  _buttonFieldCloseClicked: function(e, $editable, $field) {
+    // Content not changed: stop editing field.
+    if (!$editable.data('edit-content-changed')) {
+      // Restore to original content. When dealing with processed text, it's
+      // possible that one or more transformation filters are used. Then, the
+      // "real" original content (i.e. the transformed one) is stored separately
+      // from the "original content" that we use to detect changes.
+      if (typeof $editable.data('edit-content-original-transformed') !== 'undefined') {
+        $editable.html($editable.data('edit-content-original-transformed'));
+      }
+
+      Drupal.edit.editables.stopEdit($editable);
+    }
+    // Content changed: show modal.
+    else {
+      Drupal.edit.modal.create(
+        Drupal.t('You have unsaved changes'),
+        Drupal.theme('editButtons', { 'buttons' : [
+          { url: '#', classes: 'gray-button discard', label: Drupal.t('Discard changes') },
+          { url: '#', classes: 'blue-button save', label: Drupal.t('Save') }
+        ]}),
+        $editable
+      );
+      setTimeout(Drupal.edit.modal.show, 0);
+    };
+    return false;
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
new file mode 100644
index 0000000..5b2b655
--- /dev/null
+++ b/core/modules/edit/js/theme.js
@@ -0,0 +1,154 @@
+(function($) {
+
+/**
+ * Theme function for the overlay of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - None.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editOverlay = function(settings) {
+  var html = '';
+  html += '<div id="edit_overlay" />';
+  return html;
+};
+
+/**
+ * Theme function for a "backstage" for the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - None.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editBackstage = function(settings) {
+  var html = '';
+  html += '<div id="edit_backstage" />';
+  return html;
+};
+
+/**
+ * Theme function for a modal of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - None.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editModal = function(settings) {
+  var classes = 'edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast';
+  var html = '';
+  html += '<div id="edit_modal" class="' + classes + '">';
+  html += '  <div class="main"><p></p></div>';
+  html += '  <div class="actions"></div>';
+  html += '</div>';
+  return html;
+};
+
+/**
+ * Theme function for a toolbar container of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - id: the id to apply to the toolbar container
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editToolbarContainer = function(settings) {
+  var html = '';
+  html += '<div id="' + settings.id + '" class="edit-toolbar-container edit-animate-invisible edit-animate-only-visibility">';
+  html += '  <div class="edit-toolbar-heightfaker edit-animate-fast">';
+  html += '    <div class="edit-toolbar" />';
+  html += '  </div>';
+  html += '</div>';
+  return html;
+};
+
+/**
+ * Theme function for a toolbar toolgroup of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - classes: the class of the toolgroup
+ *   - buttons: @see Drupal.theme.prototype.editButtons().
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editToolgroup = function(settings) {
+  var classes = 'edit-toolgroup edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast';
+  var html = '';
+  html += '<div class="' + classes + ' ' + settings.classes + '">';
+  html += Drupal.theme('editButtons', { buttons: settings.buttons });
+  html += '</div>';
+  return html;
+};
+
+/**
+ * Theme function for buttons of the Edit module.
+ *
+ * Can be used for the buttons both in the toolbar toolgroups and in the modal.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - buttons: an array of objects with the following keys:
+ *     - url: the URL the button should point to
+ *     - classes: the classes of the button (optional)
+ *     - label: the label of the button (optional)
+ *     - title: the title of the button (optional)
+ *     - hasButtonRole: whether this button should have its "role" attribute set
+ *       to "button"
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editButtons = function(settings) {
+  var html = '';
+  for (var i = 0; i < settings.buttons.length; i++) {
+    var button = settings.buttons[i];
+    if (!button.hasOwnProperty('hasButtonRole')) {
+      button.hasButtonRole = true;
+    }
+
+    html += '<a href="' + button.url + '"';
+    if (button.classes) {
+      html += ' class="' + button.classes + '"';
+    }
+    if (button.title) {
+      html += ' title="' + button.title + '"';
+    }
+    html += (button.hasButtonRole) ? 'role="button"' : '';
+    html += '>';
+    if (button.label) {
+      html +=    button.label;
+    }
+    html += '</a>';
+  }
+  return html;
+};
+
+/**
+ * Theme function for a form container of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - id: the id to apply to the toolbar container
+ *   - loadingMsg: The message to show while loading.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editFormContainer = function(settings) {
+  var html = '';
+  html += '<div id="' + settings.id + '" class="edit-form-container">';
+  html += '  <div class="edit-form">';
+  html += '    <div class="placeholder">';
+  html +=        settings.loadingMsg;
+  html += '    </div>';
+  html += '  </div>';
+  html += '</div>';
+  return html;
+};
+
+})(jQuery);
diff --git a/core/modules/edit/js/ui-editables.js b/core/modules/edit/js/ui-editables.js
new file mode 100644
index 0000000..874a224
--- /dev/null
+++ b/core/modules/edit/js/ui-editables.js
@@ -0,0 +1,179 @@
+(function($) {
+
+/**
+ * @file ui-editables.js
+ *
+ * UI components for editables: toolbar, form.
+ */
+
+Drupal.edit = Drupal.edit || {};
+
+
+Drupal.edit.toolbar = {
+  create: function($editable) {
+    if (Drupal.edit.toolbar.get($editable).length > 0) {
+      return false;
+    }
+    else {
+      // Render toolbar.
+      var $toolbar = $(Drupal.theme('editToolbarContainer', {
+        id: this._id($editable)
+      }));
+
+      // Insert in DOM.
+      if ($editable.css('display') == 'inline') {
+        $toolbar.prependTo($editable.offsetParent());
+
+        var pos = $editable.position();
+        Drupal.edit.toolbar.get($editable)
+        .css('left', pos.left).css('top', pos.top);
+      }
+      else {
+        $toolbar.insertBefore($editable);
+      }
+
+      // Animate the toolbar into visibility.
+      setTimeout(function() {
+        $toolbar.removeClass('edit-animate-invisible');
+      }, 0);
+
+      // Remove any and all existing toolbars, except for any that are for a
+      // currently being edited field.
+      $('.edit-toolbar-container:not(:has(.edit-editing))')
+      .trigger('edit-toolbar-remove.edit');
+
+      // Event bindings.
+      $toolbar
+      .bind('mouseenter.edit', function(e) {
+        // Prevent triggering the entity's mouse enter event.
+        e.stopPropagation();
+      })
+      .bind('mouseleave.edit', function(e) {
+        var el = $editable[0];
+        if (e.relatedTarget != el && !jQuery.contains(el, e.relatedTarget)) {
+          console.log('triggering mouseleave on ', $editable);
+          $editable.trigger('mouseleave.edit');
+        }
+        // Prevent triggering the entity's mouse leave event.
+        e.stopPropagation();
+      })
+      // Immediate removal whenever requested.
+      // (This is necessary when showing many toolbars in rapid succession: we
+      // don't want all of them to show up!)
+      .bind('edit-toolbar-remove.edit', function(e) {
+        $toolbar.remove();
+      })
+      .delegate('.edit-toolbar, .edit-toolgroup', 'click.edit mousedown.edit', function(e) {
+        if (!$(e.target).is(':input')) {
+          return false;
+        }
+      });
+
+      return true;
+    }
+  },
+
+  get: function($editable) {
+    return ($editable.length == 0)
+      ? $([])
+      : $('#' + this._id($editable));
+  },
+
+  remove: function($editable) {
+    var $toolbar = Drupal.edit.toolbar.get($editable);
+
+    // Remove after animation.
+    $toolbar
+    .addClass('edit-animate-invisible')
+    // Prevent this toolbar from being detected *while* it is being removed.
+    .removeAttr('id')
+    .find('.edit-toolbar .edit-toolgroup')
+    .addClass('edit-animate-invisible')
+    .bind(Drupal.edit.const.transitionEnd, function(e) {
+      $toolbar.remove();
+    });
+  },
+
+  // Animate into view.
+  show: function($editable, toolgroup) {
+    this._find($editable, toolgroup).removeClass('edit-animate-invisible');
+  },
+
+  addClass: function($editable, toolgroup, classes) {
+    this._find($editable, toolgroup).addClass(classes);
+  },
+
+  removeClass: function($editable, toolgroup, classes) {
+    this._find($editable, toolgroup).removeClass(classes);
+  },
+
+  _find: function($editable, toolgroup) {
+    return Drupal.edit.toolbar.get($editable)
+           .find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+  },
+
+  _id: function($editable) {
+    var edit_id = Drupal.edit.getID(Drupal.edit.findFieldForEditable($editable));
+    return 'edit-toolbar-for-' + edit_id.split(':').join('_');
+  }
+};
+
+
+Drupal.edit.form = {
+  create: function($editable) {
+    if (Drupal.edit.form.get($editable).length > 0) {
+      return false;
+    }
+    else {
+      // Indicate in the 'info' toolgroup that the form is loading. Animated.
+      setTimeout(function() {
+        Drupal.edit.toolbar.addClass($editable, 'info', 'loading');
+      }, 0);
+
+      // Render form container.
+      var $form = $(Drupal.theme('editFormContainer', {
+        id: this._id($editable),
+        loadingMsg: Drupal.t('Loading…')}
+      ));
+
+      // Insert in DOM.
+      if ($editable.css('display') == 'inline') {
+        $form.prependTo($editable.offsetParent());
+
+        var pos = $editable.position();
+        $form.css('left', pos.left).css('top', pos.top);
+        // Reset the toolbar's positioning because it'll be moved inside the
+        // form container.
+        Drupal.edit.toolbar.get($editable).css('left', '').css('top', '');
+      }
+      else {
+        $form.insertBefore($editable);
+      }
+
+      // Move  toolbar inside .edit-form-container, to let it snap to the width
+      // of the form instead of the field formatter.
+      Drupal.edit.toolbar.get($editable).detach().prependTo('.edit-form')
+
+      return true;
+    }
+  },
+
+  get: function($editable) {
+    return ($editable.length == 0)
+      ? $([])
+      : $('#' + this._id($editable));
+  },
+
+  remove: function($editable) {
+    Drupal.edit.form.get($editable).remove();
+  },
+
+  _id: function($editable) {
+    var edit_id = ($editable.hasClass('edit-entity'))
+      ? Drupal.edit.getID($editable)
+      : Drupal.edit.getID(Drupal.edit.findFieldForEditable($editable));
+    return 'edit-form-for-' + edit_id.split(':').join('_');
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/edit/js/ui.js b/core/modules/edit/js/ui.js
new file mode 100644
index 0000000..f881527
--- /dev/null
+++ b/core/modules/edit/js/ui.js
@@ -0,0 +1,82 @@
+(function($) {
+
+/**
+ * @file ui.js
+ *
+ * "Global" UI components: toggle, modal.
+ */
+
+Drupal.edit = Drupal.edit || {};
+
+
+Drupal.edit.toggle = {
+  render: function() {
+    // TODO: fancy, "physical toggle" to switch from view to edit mode and back.
+  }
+};
+
+
+Drupal.edit.modal = {
+  create: function(message, actions, $editable) {
+    // The modal should be the only interaction element now.
+    $editable
+    .add(Drupal.edit.toolbar.get($editable))
+    .addClass('edit-belowoverlay');
+
+    $(Drupal.theme('editModal', {}))
+    .appendTo('body')
+    .find('.main p').text(message).end()
+    .find('.actions').append($(actions))
+    .delegate('a.discard', 'click.edit', function() {
+      // Restore to original content. When dealing with processed text, it's
+      // possible that one or more transformation filters are used. Then, the
+      // "real" original content (i.e. the transformed one) is stored separately
+      // from the "original content" that we use to detect changes.
+      if (typeof $editable.data('edit-content-original-transformed') !== 'undefined') {
+        $editable.html($editable.data('edit-content-original-transformed'));
+      }
+      else {
+        $editable.html($editable.data('edit-content-original'));
+      }
+
+      $editable.data('edit-content-changed', false);
+
+      Drupal.edit.modal.remove();
+      Drupal.edit.toolbar.get($editable).find('a.close').trigger('click.edit');
+      return false;
+    })
+    .delegate('a.save', 'click.edit', function() {
+      Drupal.edit.modal.remove();
+      Drupal.edit.toolbar.get($editable).find('a.save').trigger('click.edit');
+      return false;
+    });
+  },
+
+  get: function() {
+    return $('#edit_modal');
+  },
+
+  remove: function() {
+    var $modal = Drupal.edit.modal.get();
+
+    // Remove after animation.
+    $modal
+    .addClass('edit-animate-invisible')
+    .bind(Drupal.edit.const.transitionEnd, function(e) {
+      $modal.remove();
+
+      // The modal's HTML was removed, hence no need to undelegate it.
+    });
+
+    // Make the other interaction elements available again.
+    $('.edit-belowoverlay').removeClass('edit-belowoverlay');
+  },
+
+  // Animate into view.
+  show: function() {
+    Drupal.edit.modal.get()
+    .removeClass('edit-animate-invisible');
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
new file mode 100644
index 0000000..577448a
--- /dev/null
+++ b/core/modules/edit/js/util.js
@@ -0,0 +1,98 @@
+(function($) {
+
+/**
+ * @file util.js
+ *
+ * Utilities for Edit module.
+ */
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.util = Drupal.edit.util || {};
+
+Drupal.edit.util.calcFormURLForField = function(id) {
+  var parts = id.split(':');
+  var urlFormat = decodeURIComponent(Drupal.settings.edit.fieldFormURL);
+  return Drupal.formatString(urlFormat, {
+    '!entity_type': parts[0],
+    '!id'         : parts[1],
+    '!field_name' : parts[2],
+    '!langcode'   : parts[3],
+    '!view_mode'  : parts[4]
+  });
+};
+
+Drupal.edit.util.calcRerenderProcessedTextURL = function(id) {
+  var parts = id.split(':');
+  var urlFormat = decodeURIComponent(Drupal.settings.edit.rerenderProcessedTextURL);
+  return Drupal.formatString(urlFormat, {
+    '!entity_type': parts[0],
+    '!id'         : parts[1],
+    '!field_name' : parts[2],
+    '!langcode'   : parts[3],
+    '!view_mode'  : parts[4]
+  });
+}
+
+/**
+ * Get the background color of an element (or the inherited one).
+ */
+Drupal.edit.util.getBgColor = function($e) {
+  var c;
+
+  if ($e == null || $e[0].nodeName == 'HTML') {
+    // Fallback to white.
+    return 'rgb(255, 255, 255)';
+  }
+  c = $e.css('background-color');
+  // TRICKY: edge case for Firefox' "transparent" here; this is a
+  // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
+  if (c == 'rgba(0, 0, 0, 0)' || c == 'transparent') {
+    return Drupal.edit.util.getBgColor($e.parent());
+  }
+  return c;
+};
+
+/**
+ * Ignore hovering to/from the given closest element, but as soon as a hover
+ * occurs to/from *another* element, then call the given callback.
+ */
+Drupal.edit.util.ignoreHoveringVia = function(e, closest, callback) {
+  if ($(e.relatedTarget).closest(closest).length > 0) {
+    e.stopPropagation();
+  }
+  else {
+    callback();
+  }
+};
+
+/**
+ * If no position properties defined, replace value with zero.
+ */
+Drupal.edit.util.replaceBlankPosition = function(pos) {
+  if (pos == 'auto' || pos == NaN) {
+    pos = '0px';
+  }
+  return pos;
+};
+
+/**
+ * Get the top and left properties of an element and convert extraneous
+ * values and information into numbers ready for subtraction.
+ */
+Drupal.edit.util.getPositionProperties = function($e) {
+  var p,
+      r = {},
+      props = [
+        'top', 'left', 'bottom', 'right',
+        'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
+        'margin-bottom'
+      ];
+
+  for (var i = 0; i < props.length; i++) {
+    p = props[i];
+    r[p] = parseFloat(this.replaceBlankPosition($e.css(p)));
+  }
+  return r;
+};
+
+})(jQuery);
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
index 6b34ba9..85d2878 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
@@ -23,6 +23,9 @@
  *     "text",
  *     "text_long",
  *     "text_with_summary"
+ *   },
+ *   edit = {
+ *    "editability" = "direct"
  *   }
  * )
  */
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
index 0f7b615..8dc4bf1 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
@@ -23,6 +23,9 @@
  *     "text",
  *     "text_long",
  *     "text_with_summary"
+ *   },
+ *   edit = {
+ *    "editability" = "direct"
  *   }
  * )
  */
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
index 11f0c14..c69e5e0 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
@@ -22,6 +22,9 @@
  *   },
  *   settings = {
  *     "trim_length" = "600"
+ *   },
+ *   edit = {
+ *    "editability" = "form"
  *   }
  * )
  */
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
index 349cf63..add5d55 100644
--- a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
@@ -31,6 +31,9 @@
  *   },
  *   settings = {
  *     "trim_length" = "600"
+ *   },
+ *   edit = {
+ *    "editability" = "form"
  *   }
  * )
  */
