Bug 1067318 - edit as html live preview draft
authorJulian Descottes <jdescottes@mozilla.com>
Fri, 10 Feb 2017 14:55:20 +0100
changeset 581148 6eff0f199756e19a6dcf0510f4a16413358aef9a
parent 581147 84c3962d6adf1dac1d41ab2492ed61730fc4f6e9
child 581149 c5586226441ec288d3e7cbbaac7b6007dc6e5858
push id59782
push userjdescottes@mozilla.com
push dateFri, 19 May 2017 10:02:20 +0000
bugs1067318
milestone55.0a1
Bug 1067318 - edit as html live preview MozReview-Commit-ID: 3cZiw8h8WM3
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/views/html-editor.js
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -558,17 +558,17 @@ MarkupView.prototype = {
    * React to new-node-front selection events.
    * Highlights the node if needed, and make sure it is shown and selected in
    * the view.
    */
   _onNewSelection: function () {
     let selection = this.inspector.selection;
 
     if (this.htmlEditor) {
-      this.htmlEditor.hide();
+      // this.htmlEditor.hide();
     }
     if (this._hoveredNode && this._hoveredNode !== selection.nodeFront) {
       this.getContainer(this._hoveredNode).hovered = false;
       this._hoveredNode = null;
     }
 
     if (!selection.isNode()) {
       this.unmarkSelectedNode();
@@ -1283,21 +1283,27 @@ MarkupView.prototype = {
       if (this.inspector.selection.nodeFront === parentContainer.node ||
           (this.inspector.selection.nodeFront === removedNode && isHTMLTag)) {
         let childContainers = parentContainer.getChildContainers();
         if (childContainers && childContainers[childIndex]) {
           this.markNodeAsSelected(childContainers[childIndex].node, reason);
           if (childContainers[childIndex].hasChildren) {
             this.expandNode(childContainers[childIndex].node);
           }
+          clearTimeout(this.mutationTimer);
+          this.editedNode = childContainers[childIndex].node;
           this.emit("reselectedonremoved");
         }
       }
     };
 
+    this.mutationTimer = setTimeout(() => {
+      this.editedNode = removedNode;
+    }, 1000);
+
     // Start listening for mutations until we find a childList change that has
     // removedNode removed.
     this.inspector.on("markupmutation", onMutations);
   },
 
   /**
    * Make sure to stop listening for node removal markupmutations and not
    * reselect the corresponding node when that happens.
@@ -1403,26 +1409,44 @@ MarkupView.prototype = {
    * Open an editor in the UI to allow editing of a node's outerHTML.
    *
    * @param  {NodeFront} node
    *         The NodeFront to edit.
    */
   beginEditingOuterHTML: function (node) {
     this.getNodeOuterHTML(node).then(oldValue => {
       let container = this.getContainer(node);
+      this.editedNode = node;
       if (!container) {
         return;
       }
       // Load load and create HTML Editor as it is rarely used and fetch complex deps
       if (!this.htmlEditor) {
         let HTMLEditor = require("devtools/client/inspector/markup/views/html-editor");
         this.htmlEditor = new HTMLEditor(this.doc);
       }
+
+      let onHtmlEditorChange = (e, value) => {
+        if (!this.editedNode) {
+          // if the node is already being updated, don't do anything.
+          // unless we are finishing the edit, in which case we should probably wait and
+          // apply.
+          return;
+        }
+        let editedNode = this.editedNode;
+        this.editedNode = null;
+        this.updateNodeOuterHTML(editedNode, value, oldValue);
+      };
+
       this.htmlEditor.show(container.tagLine, oldValue);
+      this.htmlEditor.on("change", onHtmlEditorChange);
+
       this.htmlEditor.once("popuphidden", (e, commit, value) => {
+        this.htmlEditor.off("change", onHtmlEditorChange);
+
         // Need to focus the <html> element instead of the frame / window
         // in order to give keyboard focus back to doc (from editor).
         this.doc.documentElement.focus();
 
         if (commit) {
           this.updateNodeOuterHTML(node, value, oldValue);
         }
       });
--- a/devtools/client/inspector/markup/views/html-editor.js
+++ b/devtools/client/inspector/markup/views/html-editor.js
@@ -30,16 +30,17 @@ function HTMLEditor(htmlDocument) {
   this.container.style.display = "none";
   this.editorInner = this.doc.createElement("div");
   this.editorInner.className = "html-editor-inner";
   this.container.appendChild(this.editorInner);
 
   this.doc.body.appendChild(this.container);
   this.hide = this.hide.bind(this);
   this.refresh = this.refresh.bind(this);
+  this.onEditorChange = this.onEditorChange.bind(this);
 
   EventEmitter.decorate(this);
 
   this.doc.defaultView.addEventListener("resize",
     this.refresh, true);
 
   let config = {
     mode: Editor.modes.html,
@@ -65,17 +66,17 @@ HTMLEditor.prototype = {
 
   /**
    * Need to refresh position by manually setting CSS values, so this will
    * need to be called on resizes and other sizing changes.
    */
   refresh: function () {
     let element = this._attachedElement;
 
-    if (element) {
+    if (element && element.offsetWidth) {
       this.container.style.top = element.offsetTop + "px";
       this.container.style.left = element.offsetLeft + "px";
       this.container.style.width = element.offsetWidth + "px";
       this.container.style.height = element.parentNode.offsetHeight + "px";
       this.editor.refresh();
     }
   },
 
@@ -122,43 +123,49 @@ HTMLEditor.prototype = {
     this._originalValue = text;
     this.editor.setText(text);
     this._attach(element);
     this.container.style.display = "flex";
     this._visible = true;
 
     this.editor.refresh();
     this.editor.focus();
+    this.editor.on("change", this.onEditorChange);
 
     this.emit("popupshown");
   },
 
   /**
    * Hide the editor, optionally committing the changes
    *
    * @param  {Boolean} shouldCommit
    *         A change will be committed by default.  If this param
    *         strictly equals false, no change will occur.
    */
   hide: function (shouldCommit) {
     if (!this._visible) {
       return;
     }
 
+    this.editor.off("change", this.onEditorChange);
     this.container.style.display = "none";
     this._detach();
 
     let newValue = this.editor.getText();
     let valueHasChanged = this._originalValue !== newValue;
     let preventCommit = shouldCommit === false || !valueHasChanged;
     this._originalValue = undefined;
     this._visible = undefined;
     this.emit("popuphidden", !preventCommit, newValue);
   },
 
+  onEditorChange: function () {
+    this.emit("change", this.editor.getText());
+  },
+
   /**
    * Destroy this object and unbind all event handlers
    */
   destroy: function () {
     this.doc.defaultView.removeEventListener("resize",
       this.refresh, true);
     this.container.removeEventListener("click", this.hide);
     this.editorInner.removeEventListener("click", stopPropagation);