browser/devtools/markupview/html-editor.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/markupview/html-editor.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,186 @@
     1.4 +/* vim:set ts=2 sw=2 sts=2 et tw=80:
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 + "use strict";
     1.9 +
    1.10 +const {Cu} = require("chrome");
    1.11 +const Editor = require("devtools/sourceeditor/editor");
    1.12 +Cu.import("resource://gre/modules/Services.jsm");
    1.13 +Cu.import("resource://gre/modules/devtools/event-emitter.js");
    1.14 +
    1.15 +exports.HTMLEditor = HTMLEditor;
    1.16 +
    1.17 +function ctrl(k) {
    1.18 +  return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k;
    1.19 +}
    1.20 +function stopPropagation(e) {
    1.21 +  e.stopPropagation();
    1.22 +}
    1.23 +/**
    1.24 + * A wrapper around the Editor component, that allows editing of HTML.
    1.25 + *
    1.26 + * The main functionality this provides around the Editor is the ability
    1.27 + * to show/hide/position an editor inplace. It only appends once to the
    1.28 + * body, and uses CSS to position the editor.  The reason it is done this
    1.29 + * way is that the editor is loaded in an iframe, and calling appendChild
    1.30 + * causes it to reload.
    1.31 + *
    1.32 + * Meant to be embedded inside of an HTML page, as in markup-view.xhtml.
    1.33 + *
    1.34 + * @param HTMLDocument htmlDocument
    1.35 + *        The document to attach the editor to.  Will also use this
    1.36 + *        document as a basis for listening resize events.
    1.37 + */
    1.38 +function HTMLEditor(htmlDocument)
    1.39 +{
    1.40 +  this.doc = htmlDocument;
    1.41 +  this.container = this.doc.createElement("div");
    1.42 +  this.container.className = "html-editor theme-body";
    1.43 +  this.container.style.display = "none";
    1.44 +  this.editorInner = this.doc.createElement("div");
    1.45 +  this.editorInner.className = "html-editor-inner";
    1.46 +  this.container.appendChild(this.editorInner);
    1.47 +
    1.48 +  this.doc.body.appendChild(this.container);
    1.49 +  this.hide = this.hide.bind(this);
    1.50 +  this.refresh = this.refresh.bind(this);
    1.51 +
    1.52 +  EventEmitter.decorate(this);
    1.53 +
    1.54 +  this.doc.defaultView.addEventListener("resize",
    1.55 +    this.refresh, true);
    1.56 +
    1.57 +  let config = {
    1.58 +    mode: Editor.modes.html,
    1.59 +    lineWrapping: true,
    1.60 +    styleActiveLine: false,
    1.61 +    extraKeys: {},
    1.62 +    theme: "mozilla markup-view"
    1.63 +  };
    1.64 +
    1.65 +  config.extraKeys[ctrl("Enter")] = this.hide;
    1.66 +  config.extraKeys["F2"] = this.hide;
    1.67 +  config.extraKeys["Esc"] = this.hide.bind(this, false);
    1.68 +
    1.69 +  this.container.addEventListener("click", this.hide, false);
    1.70 +  this.editorInner.addEventListener("click", stopPropagation, false);
    1.71 +  this.editor = new Editor(config);
    1.72 +
    1.73 +  let iframe = this.editorInner.ownerDocument.createElement("iframe");
    1.74 +  this.editor.appendTo(this.editorInner, iframe).then(() => {
    1.75 +    this.hide(false);
    1.76 +  }).then(null, (err) => console.log(err.message));
    1.77 +}
    1.78 +
    1.79 +HTMLEditor.prototype = {
    1.80 +
    1.81 +  /**
    1.82 +   * Need to refresh position by manually setting CSS values, so this will
    1.83 +   * need to be called on resizes and other sizing changes.
    1.84 +   */
    1.85 +  refresh: function() {
    1.86 +    let element = this._attachedElement;
    1.87 +
    1.88 +    if (element) {
    1.89 +      this.container.style.top = element.offsetTop + "px";
    1.90 +      this.container.style.left = element.offsetLeft + "px";
    1.91 +      this.container.style.width = element.offsetWidth + "px";
    1.92 +      this.container.style.height = element.parentNode.offsetHeight + "px";
    1.93 +      this.editor.refresh();
    1.94 +    }
    1.95 +  },
    1.96 +
    1.97 +  /**
    1.98 +   * Anchor the editor to a particular element.
    1.99 +   *
   1.100 +   * @param DOMNode element
   1.101 +   *        The element that the editor will be anchored to.
   1.102 +   *        Should belong to the HTMLDocument passed into the constructor.
   1.103 +   */
   1.104 +  _attach: function(element)
   1.105 +  {
   1.106 +    this._detach();
   1.107 +    this._attachedElement = element;
   1.108 +    element.classList.add("html-editor-container");
   1.109 +    this.refresh();
   1.110 +  },
   1.111 +
   1.112 +  /**
   1.113 +   * Unanchor the editor from an element.
   1.114 +   */
   1.115 +  _detach: function()
   1.116 +  {
   1.117 +    if (this._attachedElement) {
   1.118 +      this._attachedElement.classList.remove("html-editor-container");
   1.119 +      this._attachedElement = undefined;
   1.120 +    }
   1.121 +  },
   1.122 +
   1.123 +  /**
   1.124 +   * Anchor the editor to a particular element, and show the editor.
   1.125 +   *
   1.126 +   * @param DOMNode element
   1.127 +   *        The element that the editor will be anchored to.
   1.128 +   *        Should belong to the HTMLDocument passed into the constructor.
   1.129 +   * @param string text
   1.130 +   *        Value to set the contents of the editor to
   1.131 +   * @param function cb
   1.132 +   *        The function to call when hiding
   1.133 +   */
   1.134 +  show: function(element, text)
   1.135 +  {
   1.136 +    if (this._visible) {
   1.137 +      return;
   1.138 +    }
   1.139 +
   1.140 +    this._originalValue = text;
   1.141 +    this.editor.setText(text);
   1.142 +    this._attach(element);
   1.143 +    this.container.style.display = "flex";
   1.144 +    this._visible = true;
   1.145 +
   1.146 +    this.editor.refresh();
   1.147 +    this.editor.focus();
   1.148 +
   1.149 +    this.emit("popupshown");
   1.150 +  },
   1.151 +
   1.152 +  /**
   1.153 +   * Hide the editor, optionally committing the changes
   1.154 +   *
   1.155 +   * @param bool shouldCommit
   1.156 +   *             A change will be committed by default.  If this param
   1.157 +   *             strictly equals false, no change will occur.
   1.158 +   */
   1.159 +  hide: function(shouldCommit)
   1.160 +  {
   1.161 +    if (!this._visible) {
   1.162 +      return;
   1.163 +    }
   1.164 +
   1.165 +    this.container.style.display = "none";
   1.166 +    this._detach();
   1.167 +
   1.168 +    let newValue = this.editor.getText();
   1.169 +    let valueHasChanged = this._originalValue !== newValue;
   1.170 +    let preventCommit = shouldCommit === false || !valueHasChanged;
   1.171 +    this._originalValue = undefined;
   1.172 +    this._visible = undefined;
   1.173 +    this.emit("popuphidden", !preventCommit, newValue);
   1.174 +  },
   1.175 +
   1.176 +  /**
   1.177 +   * Destroy this object and unbind all event handlers
   1.178 +   */
   1.179 +  destroy: function()
   1.180 +  {
   1.181 +    this.doc.defaultView.removeEventListener("resize",
   1.182 +      this.refresh, true);
   1.183 +    this.container.removeEventListener("click", this.hide, false);
   1.184 +    this.editorInner.removeEventListener("click", stopPropagation, false);
   1.185 +
   1.186 +    this.hide(false);
   1.187 +    this.container.parentNode.removeChild(this.container);
   1.188 +  }
   1.189 +};

mercurial